home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / lib / xulrunner-1.9.1.5 / components / nsSearchService.js < prev    next >
Text File  |  2009-11-09  |  124KB  |  3,573 lines

  1. //@line 40 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/components/search/nsSearchService.js"
  2.  
  3. const Ci = Components.interfaces;
  4. const Cc = Components.classes;
  5. const Cr = Components.results;
  6.  
  7. const PERMS_FILE      = 0644;
  8. const PERMS_DIRECTORY = 0755;
  9.  
  10. const MODE_RDONLY   = 0x01;
  11. const MODE_WRONLY   = 0x02;
  12. const MODE_CREATE   = 0x08;
  13. const MODE_APPEND   = 0x10;
  14. const MODE_TRUNCATE = 0x20;
  15.  
  16. // Directory service keys
  17. const NS_APP_SEARCH_DIR_LIST  = "SrchPluginsDL";
  18. const NS_APP_USER_SEARCH_DIR  = "UsrSrchPlugns";
  19. const NS_APP_SEARCH_DIR       = "SrchPlugns";
  20. const NS_APP_USER_PROFILE_50_DIR = "ProfD";
  21.  
  22. // Search engine "locations". If this list is changed, be sure to update
  23. // the engine's _isDefault function accordingly.
  24. const SEARCH_APP_DIR = 1;
  25. const SEARCH_PROFILE_DIR = 2;
  26. const SEARCH_IN_EXTENSION = 3;
  27.  
  28. // See documentation in nsIBrowserSearchService.idl.
  29. const SEARCH_ENGINE_TOPIC        = "browser-search-engine-modified";
  30. const QUIT_APPLICATION_TOPIC     = "quit-application";
  31.  
  32. const SEARCH_ENGINE_REMOVED      = "engine-removed";
  33. const SEARCH_ENGINE_ADDED        = "engine-added";
  34. const SEARCH_ENGINE_CHANGED      = "engine-changed";
  35. const SEARCH_ENGINE_LOADED       = "engine-loaded";
  36. const SEARCH_ENGINE_CURRENT      = "engine-current";
  37.  
  38. const SEARCH_TYPE_MOZSEARCH      = Ci.nsISearchEngine.TYPE_MOZSEARCH;
  39. const SEARCH_TYPE_OPENSEARCH     = Ci.nsISearchEngine.TYPE_OPENSEARCH;
  40. const SEARCH_TYPE_SHERLOCK       = Ci.nsISearchEngine.TYPE_SHERLOCK;
  41.  
  42. const SEARCH_DATA_XML            = Ci.nsISearchEngine.DATA_XML;
  43. const SEARCH_DATA_TEXT           = Ci.nsISearchEngine.DATA_TEXT;
  44.  
  45. // File extensions for search plugin description files
  46. const XML_FILE_EXT      = "xml";
  47. const SHERLOCK_FILE_EXT = "src";
  48.  
  49. // Delay for lazy serialization (ms)
  50. const LAZY_SERIALIZE_DELAY = 100;
  51.  
  52. // Delay for batching invalidation of the JSON cache (ms)
  53. const CACHE_INVALIDATION_DELAY = 1000;
  54.  
  55. // Current cache version. This should be incremented if the format of the cache
  56. // file is modified.
  57. const CACHE_VERSION = 5;
  58.  
  59. const ICON_DATAURL_PREFIX = "data:image/x-icon;base64,";
  60.  
  61. // Supported extensions for Sherlock plugin icons
  62. const SHERLOCK_ICON_EXTENSIONS = [".gif", ".png", ".jpg", ".jpeg"];
  63.  
  64. const NEW_LINES = /(\r\n|\r|\n)/;
  65.  
  66. // Set an arbitrary cap on the maximum icon size. Without this, large icons can
  67. // cause big delays when loading them at startup.
  68. const MAX_ICON_SIZE   = 10000;
  69.  
  70. // Default charset to use for sending search parameters. ISO-8859-1 is used to
  71. // match previous nsInternetSearchService behavior.
  72. const DEFAULT_QUERY_CHARSET = "ISO-8859-1";
  73.  
  74. const SEARCH_BUNDLE = "chrome://global/locale/search/search.properties";
  75. const BRAND_BUNDLE = "chrome://branding/locale/brand.properties";
  76.  
  77. const OPENSEARCH_NS_10  = "http://a9.com/-/spec/opensearch/1.0/";
  78. const OPENSEARCH_NS_11  = "http://a9.com/-/spec/opensearch/1.1/";
  79.  
  80. // Although the specification at http://opensearch.a9.com/spec/1.1/description/
  81. // gives the namespace names defined above, many existing OpenSearch engines
  82. // are using the following versions.  We therefore allow either.
  83. const OPENSEARCH_NAMESPACES = [
  84.   OPENSEARCH_NS_11, OPENSEARCH_NS_10,
  85.   "http://a9.com/-/spec/opensearchdescription/1.1/",
  86.   "http://a9.com/-/spec/opensearchdescription/1.0/"
  87. ];
  88.  
  89. const OPENSEARCH_LOCALNAME = "OpenSearchDescription";
  90.  
  91. const MOZSEARCH_NS_10     = "http://www.mozilla.org/2006/browser/search/";
  92. const MOZSEARCH_LOCALNAME = "SearchPlugin";
  93.  
  94. const URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
  95. const URLTYPE_SEARCH_HTML  = "text/html";
  96. const URLTYPE_OPENSEARCH   = "application/opensearchdescription+xml";
  97.  
  98. // Empty base document used to serialize engines to file.
  99. const EMPTY_DOC = "<?xml version=\"1.0\"?>\n" +
  100.                   "<" + MOZSEARCH_LOCALNAME +
  101.                   " xmlns=\"" + MOZSEARCH_NS_10 + "\"" +
  102.                   " xmlns:os=\"" + OPENSEARCH_NS_11 + "\"" +
  103.                   "/>";
  104.  
  105. const BROWSER_SEARCH_PREF = "browser.search.";
  106.  
  107. const USER_DEFINED = "{searchTerms}";
  108.  
  109. // Custom search parameters
  110. //@line 149 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/components/search/nsSearchService.js"
  111. const MOZ_OFFICIAL = "official";
  112. //@line 153 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/components/search/nsSearchService.js"
  113. const MOZ_DISTRIBUTION_ID = "com.ubuntu";
  114.  
  115. const MOZ_PARAM_LOCALE         = /\{moz:locale\}/g;
  116. const MOZ_PARAM_DIST_ID        = /\{moz:distributionID\}/g;
  117. const MOZ_PARAM_OFFICIAL       = /\{moz:official\}/g;
  118.  
  119. // Supported OpenSearch parameters
  120. // See http://opensearch.a9.com/spec/1.1/querysyntax/#core
  121. const OS_PARAM_USER_DEFINED    = /\{searchTerms\??\}/g;
  122. const OS_PARAM_INPUT_ENCODING  = /\{inputEncoding\??\}/g;
  123. const OS_PARAM_LANGUAGE        = /\{language\??\}/g;
  124. const OS_PARAM_OUTPUT_ENCODING = /\{outputEncoding\??\}/g;
  125.  
  126. // Default values
  127. const OS_PARAM_LANGUAGE_DEF         = "*";
  128. const OS_PARAM_OUTPUT_ENCODING_DEF  = "UTF-8";
  129. const OS_PARAM_INPUT_ENCODING_DEF   = "UTF-8";
  130.  
  131. // "Unsupported" OpenSearch parameters. For example, we don't support
  132. // page-based results, so if the engine requires that we send the "page index"
  133. // parameter, we'll always send "1".
  134. const OS_PARAM_COUNT        = /\{count\??\}/g;
  135. const OS_PARAM_START_INDEX  = /\{startIndex\??\}/g;
  136. const OS_PARAM_START_PAGE   = /\{startPage\??\}/g;
  137.  
  138. // Default values
  139. const OS_PARAM_COUNT_DEF        = "20"; // 20 results
  140. const OS_PARAM_START_INDEX_DEF  = "1";  // start at 1st result
  141. const OS_PARAM_START_PAGE_DEF   = "1";  // 1st page
  142.  
  143. // Optional parameter
  144. const OS_PARAM_OPTIONAL     = /\{(?:\w+:)?\w+\?\}/g;
  145.  
  146. // A array of arrays containing parameters that we don't fully support, and
  147. // their default values. We will only send values for these parameters if
  148. // required, since our values are just really arbitrary "guesses" that should
  149. // give us the output we want.
  150. var OS_UNSUPPORTED_PARAMS = [
  151.   [OS_PARAM_COUNT, OS_PARAM_COUNT_DEF],
  152.   [OS_PARAM_START_INDEX, OS_PARAM_START_INDEX_DEF],
  153.   [OS_PARAM_START_PAGE, OS_PARAM_START_PAGE_DEF],
  154. ];
  155.  
  156. // The default engine update interval, in days. This is only used if an engine
  157. // specifies an updateURL, but not an updateInterval.
  158. const SEARCH_DEFAULT_UPDATE_INTERVAL = 7;
  159.  
  160. // Returns false for whitespace-only or commented out lines in a
  161. // Sherlock file, true otherwise.
  162. function isUsefulLine(aLine) {
  163.   return !(/^\s*($|#)/i.test(aLine));
  164. }
  165.  
  166. __defineGetter__("gObsSvc", function() {
  167.   delete this.gObsSvc;
  168.   return this.gObsSvc = Cc["@mozilla.org/observer-service;1"].
  169.                         getService(Ci.nsIObserverService);
  170. });
  171.  
  172. __defineGetter__("gIoSvc", function() {
  173.   delete this.gIoSvc;
  174.   return this.gIoSvc = Cc["@mozilla.org/network/io-service;1"].
  175.                        getService(Ci.nsIIOService);
  176. });
  177.  
  178. __defineGetter__("gPrefSvc", function() {
  179.   delete this.gPrefSvc;
  180.   return this.gPrefSvc = Cc["@mozilla.org/preferences-service;1"].
  181.                          getService(Ci.nsIPrefBranch);
  182. });
  183.  
  184. /**
  185.  * Prefixed to all search debug output.
  186.  */
  187. const SEARCH_LOG_PREFIX = "*** Search: ";
  188.  
  189. /**
  190.  * Outputs aText to the JavaScript console as well as to stdout.
  191.  */
  192. function DO_LOG(aText) {
  193.   dump(SEARCH_LOG_PREFIX + aText + "\n");
  194.   var consoleService = Cc["@mozilla.org/consoleservice;1"].
  195.                        getService(Ci.nsIConsoleService);
  196.   consoleService.logStringMessage(aText);
  197. }
  198.  
  199. //@line 251 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/components/search/nsSearchService.js"
  200.  
  201. /**
  202.  * Otherwise, don't log at all by default. This can be overridden at startup
  203.  * by the pref, see SearchService's _init method.
  204.  */
  205. var LOG = function(){};
  206.  
  207. //@line 259 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/components/search/nsSearchService.js"
  208.  
  209. /**
  210.  * Presents an assertion dialog in non-release builds and throws.
  211.  * @param  message
  212.  *         A message to display
  213.  * @param  resultCode
  214.  *         The NS_ERROR_* value to throw.
  215.  * @throws resultCode
  216.  */
  217. function ERROR(message, resultCode) {
  218.   NS_ASSERT(false, SEARCH_LOG_PREFIX + message);
  219.   throw Components.Exception(message, resultCode);
  220. }
  221.  
  222. /**
  223.  * Logs the failure message (if browser.search.log is enabled) and throws.
  224.  * @param  message
  225.  *         A message to display
  226.  * @param  resultCode
  227.  *         The NS_ERROR_* value to throw.
  228.  * @throws resultCode or NS_ERROR_INVALID_ARG if resultCode isn't specified.
  229.  */
  230. function FAIL(message, resultCode) {
  231.   LOG(message);
  232.   throw Components.Exception(message, resultCode || Cr.NS_ERROR_INVALID_ARG);
  233. }
  234.  
  235. /**
  236.  * Ensures an assertion is met before continuing. Should be used to indicate
  237.  * fatal errors.
  238.  * @param  assertion
  239.  *         An assertion that must be met
  240.  * @param  message
  241.  *         A message to display if the assertion is not met
  242.  * @param  resultCode
  243.  *         The NS_ERROR_* value to throw if the assertion is not met
  244.  * @throws resultCode
  245.  */
  246. function ENSURE_WARN(assertion, message, resultCode) {
  247.   NS_ASSERT(assertion, SEARCH_LOG_PREFIX + message);
  248.   if (!assertion)
  249.     throw Components.Exception(message, resultCode);
  250. }
  251.  
  252. function loadListener(aChannel, aEngine, aCallback) {
  253.   this._channel = aChannel;
  254.   this._bytes = [];
  255.   this._engine = aEngine;
  256.   this._callback = aCallback;
  257. }
  258. loadListener.prototype = {
  259.   _callback: null,
  260.   _channel: null,
  261.   _countRead: 0,
  262.   _engine: null,
  263.   _stream: null,
  264.  
  265.   QueryInterface: function SRCH_loadQI(aIID) {
  266.     if (aIID.equals(Ci.nsISupports)           ||
  267.         aIID.equals(Ci.nsIRequestObserver)    ||
  268.         aIID.equals(Ci.nsIStreamListener)     ||
  269.         aIID.equals(Ci.nsIChannelEventSink)   ||
  270.         aIID.equals(Ci.nsIInterfaceRequestor) ||
  271.         aIID.equals(Ci.nsIBadCertListener2)   ||
  272.         aIID.equals(Ci.nsISSLErrorListener)   ||
  273.         // See FIXME comment below
  274.         aIID.equals(Ci.nsIHttpEventSink)      ||
  275.         aIID.equals(Ci.nsIProgressEventSink)  ||
  276.         false)
  277.       return this;
  278.  
  279.     throw Cr.NS_ERROR_NO_INTERFACE;
  280.   },
  281.  
  282.   // nsIRequestObserver
  283.   onStartRequest: function SRCH_loadStartR(aRequest, aContext) {
  284.     LOG("loadListener: Starting request: " + aRequest.name);
  285.     this._stream = Cc["@mozilla.org/binaryinputstream;1"].
  286.                    createInstance(Ci.nsIBinaryInputStream);
  287.   },
  288.  
  289.   onStopRequest: function SRCH_loadStopR(aRequest, aContext, aStatusCode) {
  290.     LOG("loadListener: Stopping request: " + aRequest.name);
  291.  
  292.     var requestFailed = !Components.isSuccessCode(aStatusCode);
  293.     if (!requestFailed && (aRequest instanceof Ci.nsIHttpChannel))
  294.       requestFailed = !aRequest.requestSucceeded;
  295.  
  296.     if (requestFailed || this._countRead == 0) {
  297.       LOG("loadListener: request failed!");
  298.       // send null so the callback can deal with the failure
  299.       this._callback(null, this._engine);
  300.     } else
  301.       this._callback(this._bytes, this._engine);
  302.     this._channel = null;
  303.     this._engine  = null;
  304.   },
  305.  
  306.   // nsIStreamListener
  307.   onDataAvailable: function SRCH_loadDAvailable(aRequest, aContext,
  308.                                                 aInputStream, aOffset,
  309.                                                 aCount) {
  310.     this._stream.setInputStream(aInputStream);
  311.  
  312.     // Get a byte array of the data
  313.     this._bytes = this._bytes.concat(this._stream.readByteArray(aCount));
  314.     this._countRead += aCount;
  315.   },
  316.  
  317.   // nsIChannelEventSink
  318.   onChannelRedirect: function SRCH_loadCRedirect(aOldChannel, aNewChannel,
  319.                                                  aFlags) {
  320.     this._channel = aNewChannel;
  321.   },
  322.  
  323.   // nsIInterfaceRequestor
  324.   getInterface: function SRCH_load_GI(aIID) {
  325.     return this.QueryInterface(aIID);
  326.   },
  327.  
  328.   // nsIBadCertListener2
  329.   notifyCertProblem: function SRCH_certProblem(socketInfo, status, targetSite) {
  330.     return true;
  331.   },
  332.  
  333.   // nsISSLErrorListener
  334.   notifySSLError: function SRCH_SSLError(socketInfo, error, targetSite) {
  335.     return true;
  336.   },
  337.  
  338.   // FIXME: bug 253127
  339.   // nsIHttpEventSink
  340.   onRedirect: function (aChannel, aNewChannel) {},
  341.   // nsIProgressEventSink
  342.   onProgress: function (aRequest, aContext, aProgress, aProgressMax) {},
  343.   onStatus: function (aRequest, aContext, aStatus, aStatusArg) {}
  344. }
  345.  
  346.  
  347. /**
  348.  * Used to verify a given DOM node's localName and namespaceURI.
  349.  * @param aElement
  350.  *        The element to verify.
  351.  * @param aLocalNameArray
  352.  *        An array of strings to compare against aElement's localName.
  353.  * @param aNameSpaceArray
  354.  *        An array of strings to compare against aElement's namespaceURI.
  355.  *
  356.  * @returns false if aElement is null, or if its localName or namespaceURI
  357.  *          does not match one of the elements in the aLocalNameArray or
  358.  *          aNameSpaceArray arrays, respectively.
  359.  * @throws NS_ERROR_INVALID_ARG if aLocalNameArray or aNameSpaceArray are null.
  360.  */
  361. function checkNameSpace(aElement, aLocalNameArray, aNameSpaceArray) {
  362.   if (!aLocalNameArray || !aNameSpaceArray)
  363.     FAIL("missing aLocalNameArray or aNameSpaceArray for checkNameSpace");
  364.   return (aElement                                                &&
  365.           (aLocalNameArray.indexOf(aElement.localName)    != -1)  &&
  366.           (aNameSpaceArray.indexOf(aElement.namespaceURI) != -1));
  367. }
  368.  
  369. /**
  370.  * Safely close a nsISafeOutputStream.
  371.  * @param aFOS
  372.  *        The file output stream to close.
  373.  */
  374. function closeSafeOutputStream(aFOS) {
  375.   if (aFOS instanceof Ci.nsISafeOutputStream) {
  376.     try {
  377.       aFOS.finish();
  378.       return;
  379.     } catch (e) { }
  380.   }
  381.   aFOS.close();
  382. }
  383.  
  384. /**
  385.  * Wrapper function for nsIIOService::newURI.
  386.  * @param aURLSpec
  387.  *        The URL string from which to create an nsIURI.
  388.  * @returns an nsIURI object, or null if the creation of the URI failed.
  389.  */
  390. function makeURI(aURLSpec, aCharset) {
  391.   try {
  392.     return gIoSvc.newURI(aURLSpec, aCharset, null);
  393.   } catch (ex) { }
  394.  
  395.   return null;
  396. }
  397.  
  398. /**
  399.  * Gets a directory from the directory service.
  400.  * @param aKey
  401.  *        The directory service key indicating the directory to get.
  402.  */
  403. let _dirSvc = null;
  404. function getDir(aKey, aIFace) {
  405.   if (!aKey)
  406.     FAIL("getDir requires a directory key!");
  407.  
  408.   if (!_dirSvc)
  409.     _dirSvc = Cc["@mozilla.org/file/directory_service;1"].
  410.                getService(Ci.nsIProperties);
  411.   return _dirSvc.get(aKey, aIFace || Ci.nsIFile);
  412. }
  413.  
  414. /**
  415.  * The following two functions are essentially copied from
  416.  * nsInternetSearchService. They are required for backwards compatibility.
  417.  */
  418. function queryCharsetFromCode(aCode) {
  419.   const codes = [];
  420.   codes[0] = "x-mac-roman";
  421.   codes[6] = "x-mac-greek";
  422.   codes[35] = "x-mac-turkish";
  423.   codes[513] = "ISO-8859-1";
  424.   codes[514] = "ISO-8859-2";
  425.   codes[517] = "ISO-8859-5";
  426.   codes[518] = "ISO-8859-6";
  427.   codes[519] = "ISO-8859-7";
  428.   codes[520] = "ISO-8859-8";
  429.   codes[521] = "ISO-8859-9";
  430.   codes[1049] = "IBM864";
  431.   codes[1280] = "windows-1252";
  432.   codes[1281] = "windows-1250";
  433.   codes[1282] = "windows-1251";
  434.   codes[1283] = "windows-1253";
  435.   codes[1284] = "windows-1254";
  436.   codes[1285] = "windows-1255";
  437.   codes[1286] = "windows-1256";
  438.   codes[1536] = "us-ascii";
  439.   codes[1584] = "GB2312";
  440.   codes[1585] = "x-gbk";
  441.   codes[1600] = "EUC-KR";
  442.   codes[2080] = "ISO-2022-JP";
  443.   codes[2096] = "ISO-2022-CN";
  444.   codes[2112] = "ISO-2022-KR";
  445.   codes[2336] = "EUC-JP";
  446.   codes[2352] = "GB2312";
  447.   codes[2353] = "x-euc-tw";
  448.   codes[2368] = "EUC-KR";
  449.   codes[2561] = "Shift_JIS";
  450.   codes[2562] = "KOI8-R";
  451.   codes[2563] = "Big5";
  452.   codes[2565] = "HZ-GB-2312";
  453.  
  454.   if (codes[aCode])
  455.     return codes[aCode];
  456.  
  457.   return getLocalizedPref("intl.charset.default", DEFAULT_QUERY_CHARSET);
  458. }
  459. function fileCharsetFromCode(aCode) {
  460.   const codes = [
  461.     "x-mac-roman",           // 0
  462.     "Shift_JIS",             // 1
  463.     "Big5",                  // 2
  464.     "EUC-KR",                // 3
  465.     "X-MAC-ARABIC",          // 4
  466.     "X-MAC-HEBREW",          // 5
  467.     "X-MAC-GREEK",           // 6
  468.     "X-MAC-CYRILLIC",        // 7
  469.     "X-MAC-DEVANAGARI" ,     // 9
  470.     "X-MAC-GURMUKHI",        // 10
  471.     "X-MAC-GUJARATI",        // 11
  472.     "X-MAC-ORIYA",           // 12
  473.     "X-MAC-BENGALI",         // 13
  474.     "X-MAC-TAMIL",           // 14
  475.     "X-MAC-TELUGU",          // 15
  476.     "X-MAC-KANNADA",         // 16
  477.     "X-MAC-MALAYALAM",       // 17
  478.     "X-MAC-SINHALESE",       // 18
  479.     "X-MAC-BURMESE",         // 19
  480.     "X-MAC-KHMER",           // 20
  481.     "X-MAC-THAI",            // 21
  482.     "X-MAC-LAOTIAN",         // 22
  483.     "X-MAC-GEORGIAN",        // 23
  484.     "X-MAC-ARMENIAN",        // 24
  485.     "GB2312",                // 25
  486.     "X-MAC-TIBETAN",         // 26
  487.     "X-MAC-MONGOLIAN",       // 27
  488.     "X-MAC-ETHIOPIC",        // 28
  489.     "X-MAC-CENTRALEURROMAN", // 29
  490.     "X-MAC-VIETNAMESE",      // 30
  491.     "X-MAC-EXTARABIC"        // 31
  492.   ];
  493.   // Sherlock files have always defaulted to x-mac-roman, so do that here too
  494.   return codes[aCode] || codes[0];
  495. }
  496.  
  497. /**
  498.  * Returns a string interpretation of aBytes using aCharset, or null on
  499.  * failure.
  500.  */
  501. function bytesToString(aBytes, aCharset) {
  502.   var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  503.                   createInstance(Ci.nsIScriptableUnicodeConverter);
  504.   LOG("bytesToString: converting using charset: " + aCharset);
  505.  
  506.   try {
  507.     converter.charset = aCharset;
  508.     return converter.convertFromByteArray(aBytes, aBytes.length);
  509.   } catch (ex) {}
  510.  
  511.   return null;
  512. }
  513.  
  514. /**
  515.  * Converts an array of bytes representing a Sherlock file into an array of
  516.  * lines representing the useful data from the file.
  517.  *
  518.  * @param aBytes
  519.  *        The array of bytes representing the Sherlock file.
  520.  * @param aCharsetCode
  521.  *        An integer value representing a character set code to be passed to
  522.  *        fileCharsetFromCode, or null for the default Sherlock encoding.
  523.  */
  524. function sherlockBytesToLines(aBytes, aCharsetCode) {
  525.   // fileCharsetFromCode returns the default encoding if aCharsetCode is null
  526.   var charset = fileCharsetFromCode(aCharsetCode);
  527.  
  528.   var dataString = bytesToString(aBytes, charset);
  529.   if (!dataString)
  530.     FAIL("sherlockBytesToLines: Couldn't convert byte array!", Cr.NS_ERROR_FAILURE);
  531.  
  532.   // Split the string into lines, and filter out comments and
  533.   // whitespace-only lines
  534.   return dataString.split(NEW_LINES).filter(isUsefulLine);
  535. }
  536.  
  537. /**
  538.  * Gets the current value of the locale.  It's possible for this preference to
  539.  * be localized, so we have to do a little extra work here.  Similar code
  540.  * exists in nsHttpHandler.cpp when building the UA string.
  541.  */
  542. function getLocale() {
  543.   const localePref = "general.useragent.locale";
  544.   var locale = getLocalizedPref(localePref);
  545.   if (locale)
  546.     return locale;
  547.  
  548.   // Not localized
  549.   return gPrefSvc.getCharPref(localePref);
  550. }
  551.  
  552. /**
  553.  * Wrapper for nsIPrefBranch::getComplexValue.
  554.  * @param aPrefName
  555.  *        The name of the pref to get.
  556.  * @returns aDefault if the requested pref doesn't exist.
  557.  */
  558. function getLocalizedPref(aPrefName, aDefault) {
  559.   const nsIPLS = Ci.nsIPrefLocalizedString;
  560.   try {
  561.     return gPrefSvc.getComplexValue(aPrefName, nsIPLS).data;
  562.   } catch (ex) {}
  563.  
  564.   return aDefault;
  565. }
  566.  
  567. /**
  568.  * Wrapper for nsIPrefBranch::setComplexValue.
  569.  * @param aPrefName
  570.  *        The name of the pref to set.
  571.  */
  572. function setLocalizedPref(aPrefName, aValue) {
  573.   const nsIPLS = Ci.nsIPrefLocalizedString;
  574.   try {
  575.     var pls = Components.classes["@mozilla.org/pref-localizedstring;1"]
  576.                         .createInstance(Ci.nsIPrefLocalizedString);
  577.     pls.data = aValue;
  578.     gPrefSvc.setComplexValue(aPrefName, nsIPLS, pls);
  579.   } catch (ex) {}
  580. }
  581.  
  582. /**
  583.  * Wrapper for nsIPrefBranch::getBoolPref.
  584.  * @param aPrefName
  585.  *        The name of the pref to get.
  586.  * @returns aDefault if the requested pref doesn't exist.
  587.  */
  588. function getBoolPref(aName, aDefault) {
  589.   try {
  590.     return gPrefSvc.getBoolPref(aName);
  591.   } catch (ex) {
  592.     return aDefault;
  593.   }
  594. }
  595.  
  596. /**
  597.  * Get a unique nsIFile object with a sanitized name, based on the engine name.
  598.  * @param aName
  599.  *        A name to "sanitize". Can be an empty string, in which case a random
  600.  *        8 character filename will be produced.
  601.  * @returns A nsIFile object in the user's search engines directory with a
  602.  *          unique sanitized name.
  603.  */
  604. function getSanitizedFile(aName) {
  605.   var fileName = sanitizeName(aName) + "." + XML_FILE_EXT;
  606.   var file = getDir(NS_APP_USER_SEARCH_DIR);
  607.   file.append(fileName);
  608.   file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
  609.   return file;
  610. }
  611.  
  612. /**
  613.  * Removes all characters not in the "chars" string from aName.
  614.  *
  615.  * @returns a sanitized name to be used as a filename, or a random name
  616.  *          if a sanitized name cannot be obtained (if aName contains
  617.  *          no valid characters).
  618.  */
  619. function sanitizeName(aName) {
  620.   const chars = "-abcdefghijklmnopqrstuvwxyz0123456789";
  621.   const maxLength = 60;
  622.  
  623.   var name = aName.toLowerCase();
  624.   name = name.replace(/ /g, "-");
  625.   name = name.split("").filter(function (el) {
  626.                                  return chars.indexOf(el) != -1;
  627.                                }).join("");
  628.  
  629.   if (!name) {
  630.     // Our input had no valid characters - use a random name
  631.     var cl = chars.length - 1;
  632.     for (var i = 0; i < 8; ++i)
  633.       name += chars.charAt(Math.round(Math.random() * cl));
  634.   }
  635.  
  636.   if (name.length > maxLength)
  637.     name = name.substring(0, maxLength);
  638.  
  639.   return name;
  640. }
  641.  
  642. /**
  643.  * Retrieve a pref from the search param branch.
  644.  *
  645.  * @param prefName
  646.  *        The name of the pref.
  647.  **/
  648. function getMozParamPref(prefName)
  649.   gPrefSvc.getCharPref(BROWSER_SEARCH_PREF + "param." + prefName);
  650.  
  651. /**
  652.  * Notifies watchers of SEARCH_ENGINE_TOPIC about changes to an engine or to
  653.  * the state of the search service.
  654.  *
  655.  * @param aEngine
  656.  *        The nsISearchEngine object to which the change applies.
  657.  * @param aVerb
  658.  *        A verb describing the change.
  659.  *
  660.  * @see nsIBrowserSearchService.idl
  661.  */
  662. function notifyAction(aEngine, aVerb) {
  663.   LOG("NOTIFY: Engine: \"" + aEngine.name + "\"; Verb: \"" + aVerb + "\"");
  664.   gObsSvc.notifyObservers(aEngine, SEARCH_ENGINE_TOPIC, aVerb);
  665. }
  666.  
  667. /**
  668.  * Simple object representing a name/value pair.
  669.  */
  670. function QueryParameter(aName, aValue) {
  671.   if (!aName || (aValue == null))
  672.     FAIL("missing name or value for QueryParameter!");
  673.  
  674.   this.name = aName;
  675.   this.value = aValue;
  676. }
  677.  
  678. /**
  679.  * Perform OpenSearch parameter substitution on aParamValue.
  680.  *
  681.  * @param aParamValue
  682.  *        A string containing OpenSearch search parameters.
  683.  * @param aSearchTerms
  684.  *        The user-provided search terms. This string will inserted into
  685.  *        aParamValue as the value of the OS_PARAM_USER_DEFINED parameter.
  686.  *        This value must already be escaped appropriately - it is inserted
  687.  *        as-is.
  688.  * @param aQueryEncoding
  689.  *        The value to use for the OS_PARAM_INPUT_ENCODING parameter. See
  690.  *        definition in the OpenSearch spec.
  691.  *
  692.  * @see http://opensearch.a9.com/spec/1.1/querysyntax/#core
  693.  */
  694. function ParamSubstitution(aParamValue, aSearchTerms, aEngine) {
  695.   var value = aParamValue;
  696.  
  697.   var distributionID = MOZ_DISTRIBUTION_ID;
  698.   try {
  699.     distributionID = gPrefSvc.getCharPref(BROWSER_SEARCH_PREF + "distributionID");
  700.   }
  701.   catch (ex) { }
  702.  
  703.   // Custom search parameters. These are only available to default search
  704.   // engines.
  705.   if (aEngine._isDefault) {
  706.     value = value.replace(MOZ_PARAM_LOCALE, getLocale());
  707.     value = value.replace(MOZ_PARAM_DIST_ID, distributionID);
  708.     value = value.replace(MOZ_PARAM_OFFICIAL, MOZ_OFFICIAL);
  709.   }
  710.  
  711.   // Insert the OpenSearch parameters we're confident about
  712.   value = value.replace(OS_PARAM_USER_DEFINED, aSearchTerms);
  713.   value = value.replace(OS_PARAM_INPUT_ENCODING, aEngine.queryCharset);
  714.   value = value.replace(OS_PARAM_LANGUAGE,
  715.                         getLocale() || OS_PARAM_LANGUAGE_DEF);
  716.   value = value.replace(OS_PARAM_OUTPUT_ENCODING,
  717.                         OS_PARAM_OUTPUT_ENCODING_DEF);
  718.  
  719.   // Replace any optional parameters
  720.   value = value.replace(OS_PARAM_OPTIONAL, "");
  721.  
  722.   // Insert any remaining required params with our default values
  723.   for (var i = 0; i < OS_UNSUPPORTED_PARAMS.length; ++i) {
  724.     value = value.replace(OS_UNSUPPORTED_PARAMS[i][0],
  725.                           OS_UNSUPPORTED_PARAMS[i][1]);
  726.   }
  727.  
  728.   return value;
  729. }
  730.  
  731. /**
  732.  * Creates a mozStorage statement that can be used to access the database we
  733.  * use to hold metadata.
  734.  *
  735.  * @param dbconn  the database that the statement applies to
  736.  * @param sql     a string specifying the sql statement that should be created
  737.  */
  738. function createStatement (dbconn, sql) {
  739.   var stmt = dbconn.createStatement(sql);
  740.   var wrapper = Cc["@mozilla.org/storage/statement-wrapper;1"].
  741.                 createInstance(Ci.mozIStorageStatementWrapper);
  742.  
  743.   wrapper.initialize(stmt);
  744.   return wrapper;
  745. }
  746.  
  747. /**
  748.  * Creates an engineURL object, which holds the query URL and all parameters.
  749.  *
  750.  * @param aType
  751.  *        A string containing the name of the MIME type of the search results
  752.  *        returned by this URL.
  753.  * @param aMethod
  754.  *        The HTTP request method. Must be a case insensitive value of either
  755.  *        "GET" or "POST".
  756.  * @param aTemplate
  757.  *        The URL to which search queries should be sent. For GET requests,
  758.  *        must contain the string "{searchTerms}", to indicate where the user
  759.  *        entered search terms should be inserted.
  760.  *
  761.  * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
  762.  *
  763.  * @throws NS_ERROR_NOT_IMPLEMENTED if aType is unsupported.
  764.  */
  765. function EngineURL(aType, aMethod, aTemplate) {
  766.   if (!aType || !aMethod || !aTemplate)
  767.     FAIL("missing type, method or template for EngineURL!");
  768.  
  769.   var method = aMethod.toUpperCase();
  770.   var type   = aType.toLowerCase();
  771.  
  772.   if (method != "GET" && method != "POST")
  773.     FAIL("method passed to EngineURL must be \"GET\" or \"POST\"");
  774.  
  775.   this.type     = type;
  776.   this.method   = method;
  777.   this.params   = [];
  778.   this.rels     = [];
  779.   // Don't serialize expanded mozparams
  780.   this.mozparams = {};
  781.  
  782.   var templateURI = makeURI(aTemplate);
  783.   if (!templateURI)
  784.     FAIL("new EngineURL: template is not a valid URI!", Cr.NS_ERROR_FAILURE);
  785.  
  786.   switch (templateURI.scheme) {
  787.     case "http":
  788.     case "https":
  789.     // Disable these for now, see bug 295018
  790.     // case "file":
  791.     // case "resource":
  792.       this.template = aTemplate;
  793.       break;
  794.     default:
  795.       FAIL("new EngineURL: template uses invalid scheme!", Cr.NS_ERROR_FAILURE);
  796.   }
  797. }
  798. EngineURL.prototype = {
  799.  
  800.   addParam: function SRCH_EURL_addParam(aName, aValue) {
  801.     this.params.push(new QueryParameter(aName, aValue));
  802.   },
  803.  
  804.   _addMozParam: function SRCH_EURL__addMozParam(aObj) {
  805.     aObj.mozparam = true;
  806.     this.mozparams[aObj.name] = aObj;
  807.   },
  808.  
  809.   getSubmission: function SRCH_EURL_getSubmission(aSearchTerms, aEngine) {
  810.     var url = ParamSubstitution(this.template, aSearchTerms, aEngine);
  811.  
  812.     // Create an application/x-www-form-urlencoded representation of our params
  813.     // (name=value&name=value&name=value)
  814.     var dataString = "";
  815.     for (var i = 0; i < this.params.length; ++i) {
  816.       var param = this.params[i];
  817.       var value = ParamSubstitution(param.value, aSearchTerms, aEngine);
  818.  
  819.       dataString += (i > 0 ? "&" : "") + param.name + "=" + value;
  820.     }
  821.  
  822.     var postData = null;
  823.     if (this.method == "GET") {
  824.       // GET method requests have no post data, and append the encoded
  825.       // query string to the url...
  826.       if (url.indexOf("?") == -1 && dataString)
  827.         url += "?";
  828.       url += dataString;
  829.     } else if (this.method == "POST") {
  830.       // POST method requests must wrap the encoded text in a MIME
  831.       // stream and supply that as POSTDATA.
  832.       var stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
  833.                          createInstance(Ci.nsIStringInputStream);
  834.       stringStream.data = dataString;
  835.  
  836.       postData = Cc["@mozilla.org/network/mime-input-stream;1"].
  837.                  createInstance(Ci.nsIMIMEInputStream);
  838.       postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
  839.       postData.addContentLength = true;
  840.       postData.setData(stringStream);
  841.     }
  842.  
  843.     return new Submission(makeURI(url), postData);
  844.   },
  845.  
  846.   _hasRelation: function SRC_EURL__hasRelation(aRel)
  847.     this.rels.some(function(e) e == aRel.toLowerCase()),
  848.  
  849.   _initWithJSON: function SRC_EURL__initWithJSON(aJson, aEngine) {
  850.     if (!aJson.params)
  851.       return;
  852.  
  853.     this.rels = aJson.rels;
  854.  
  855.     for (let i = 0; i < aJson.params.length; ++i) {
  856.       let param = aJson.params[i];
  857.       if (param.mozparam) {
  858.         if (param.condition == "defaultEngine") {
  859.           if (aEngine._isDefaultEngine())
  860.             this.addParam(param.name, param.trueValue);
  861.           else
  862.             this.addParam(param.name, param.falseValue);
  863.         } else if (param.condition == "pref") {
  864.           let value = getMozParamPref(param.pref);
  865.           this.addParam(param.name, value);
  866.         }
  867.         this._addMozParam(param);
  868.       }
  869.       else
  870.         this.addParam(param.name, param.value);
  871.     }
  872.   },
  873.  
  874.   /**
  875.    * Creates a JavaScript object that represents this URL.
  876.    * @returns An object suitable for serialization as JSON.
  877.    **/
  878.   _serializeToJSON: function SRCH_EURL__serializeToJSON() {
  879.     var json = {
  880.       template: this.template,
  881.       rels: this.rels
  882.     };
  883.  
  884.     if (this.type != URLTYPE_SEARCH_HTML)
  885.       json.type = this.type;
  886.     if (this.method != "GET")
  887.       json.method = this.method;
  888.  
  889.     function collapseMozParams(aParam)
  890.       this.mozparams[aParam.name] || aParam;
  891.     json.params = this.params.map(collapseMozParams, this);
  892.  
  893.     return json;
  894.   },
  895.  
  896.   /**
  897.    * Serializes the engine object to a OpenSearch Url element.
  898.    * @param aDoc
  899.    *        The document to use to create the Url element.
  900.    * @param aElement
  901.    *        The element to which the created Url element is appended.
  902.    *
  903.    * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
  904.    */
  905.   _serializeToElement: function SRCH_EURL_serializeToEl(aDoc, aElement) {
  906.     var url = aDoc.createElementNS(OPENSEARCH_NS_11, "Url");
  907.     url.setAttribute("type", this.type);
  908.     url.setAttribute("method", this.method);
  909.     url.setAttribute("template", this.template);
  910.     if (this.rels.length)
  911.       url.setAttribute("rel", this.rels.join(" "));
  912.  
  913.     for (var i = 0; i < this.params.length; ++i) {
  914.       var param = aDoc.createElementNS(OPENSEARCH_NS_11, "Param");
  915.       param.setAttribute("name", this.params[i].name);
  916.       param.setAttribute("value", this.params[i].value);
  917.       url.appendChild(aDoc.createTextNode("\n  "));
  918.       url.appendChild(param);
  919.     }
  920.     url.appendChild(aDoc.createTextNode("\n"));
  921.     aElement.appendChild(url);
  922.   }
  923. };
  924.  
  925. /**
  926.  * nsISearchEngine constructor.
  927.  * @param aLocation
  928.  *        A nsILocalFile or nsIURI object representing the location of the
  929.  *        search engine data file.
  930.  * @param aSourceDataType
  931.  *        The data type of the file used to describe the engine. Must be either
  932.  *        DATA_XML or DATA_TEXT.
  933.  * @param aIsReadOnly
  934.  *        Boolean indicating whether the engine should be treated as read-only.
  935.  *        Read only engines cannot be serialized to file.
  936.  */
  937. function Engine(aLocation, aSourceDataType, aIsReadOnly) {
  938.   this._dataType = aSourceDataType;
  939.   this._readOnly = aIsReadOnly;
  940.   this._urls = [];
  941.  
  942.   if (aLocation.cached) {
  943.     this._file = aLocation.value;
  944.   } else if (aLocation instanceof Ci.nsILocalFile) {
  945.     // we already have a file (e.g. loading engines from disk)
  946.     this._file = aLocation;
  947.   } else if (aLocation instanceof Ci.nsIURI) {
  948.     this._uri = aLocation;
  949.     switch (aLocation.scheme) {
  950.       case "https":
  951.       case "http":
  952.       case "ftp":
  953.       case "data":
  954.       case "file":
  955.       case "resource":
  956.         this._uri = aLocation;
  957.         break;
  958.       default:
  959.         ERROR("Invalid URI passed to the nsISearchEngine constructor",
  960.               Cr.NS_ERROR_INVALID_ARG);
  961.     }
  962.   } else
  963.     ERROR("Engine location is neither a File nor a URI object",
  964.           Cr.NS_ERROR_INVALID_ARG);
  965. }
  966.  
  967. Engine.prototype = {
  968.   // The engine's alias.
  969.   _alias: null,
  970.   // The data describing the engine. Is either an array of bytes, for Sherlock
  971.   // files, or an XML document element, for XML plugins.
  972.   _data: null,
  973.   // The engine's data type. See data types (DATA_) defined above.
  974.   _dataType: null,
  975.   // Whether or not the engine is readonly.
  976.   _readOnly: true,
  977.   // The engine's description
  978.   _description: "",
  979.   // Used to store the engine to replace, if we're an update to an existing
  980.   // engine.
  981.   _engineToUpdate: null,
  982.   // The file from which the plugin was loaded.
  983.   __file: null,
  984.   get _file() {
  985.     if (this.__file && !(this.__file instanceof Ci.nsILocalFile)) {
  986.       let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
  987.       file.persistentDescriptor = this.__file;
  988.       return this.__file = file;
  989.     }
  990.     return this.__file;
  991.   },
  992.   set _file(aValue) {
  993.     this.__file = aValue;
  994.   },
  995.   // Set to true if the engine has a preferred icon (an icon that should not be
  996.   // overridden by a non-preferred icon).
  997.   _hasPreferredIcon: null,
  998.   // Whether the engine is hidden from the user.
  999.   _hidden: null,
  1000.   // The engine's name.
  1001.   _name: null,
  1002.   // The engine type. See engine types (TYPE_) defined above.
  1003.   _type: null,
  1004.   // The name of the charset used to submit the search terms.
  1005.   _queryCharset: null,
  1006.   // A URL string pointing to the engine's search form.
  1007.   __searchForm: null,
  1008.   get _searchForm() {
  1009.     return this.__searchForm;
  1010.   },
  1011.   set _searchForm(aValue) {
  1012.     if (/^https?:/i.test(aValue))
  1013.       this.__searchForm = aValue;
  1014.     else
  1015.       LOG("_searchForm: Invalid URL dropped for " + this._name ||
  1016.           "the current engine");
  1017.   },
  1018.   // The URI object from which the engine was retrieved.
  1019.   // This is null for local plugins, and is used for error messages and logging.
  1020.   _uri: null,
  1021.   // Whether to obtain user confirmation before adding the engine. This is only
  1022.   // used when the engine is first added to the list.
  1023.   _confirm: false,
  1024.   // Whether to set this as the current engine as soon as it is loaded.  This
  1025.   // is only used when the engine is first added to the list.
  1026.   _useNow: true,
  1027.   // Where the engine was loaded from. Can be one of: SEARCH_APP_DIR,
  1028.   // SEARCH_PROFILE_DIR, SEARCH_IN_EXTENSION.
  1029.   __installLocation: null,
  1030.   // The number of days between update checks for new versions
  1031.   _updateInterval: null,
  1032.   // The url to check at for a new update
  1033.   _updateURL: null,
  1034.   // The url to check for a new icon
  1035.   _iconUpdateURL: null,
  1036.   // A reference to the timer used for lazily serializing the engine to file
  1037.   _serializeTimer: null,
  1038.   // Whether this engine has been used since the cache was last recreated.
  1039.   __used: null,
  1040.   get _used() {
  1041.     if (!this.__used)
  1042.       this.__used = !!engineMetadataService.getAttr(this, "used");
  1043.     return this.__used;
  1044.   },
  1045.   set _used(aValue) {
  1046.     this.__used = aValue
  1047.     engineMetadataService.setAttr(this, "used", aValue);
  1048.   },
  1049.  
  1050.   /**
  1051.    * Retrieves the data from the engine's file. If the engine's dataType is
  1052.    * XML, the document element is placed in the engine's data field. For text
  1053.    * engines, the data is just read directly from file and placed as an array
  1054.    * of lines in the engine's data field.
  1055.    */
  1056.   _initFromFile: function SRCH_ENG_initFromFile() {
  1057.     if (!this._file || !this._file.exists())
  1058.       FAIL("File must exist before calling initFromFile!", Cr.NS_ERROR_UNEXPECTED);
  1059.  
  1060.     var fileInStream = Cc["@mozilla.org/network/file-input-stream;1"].
  1061.                        createInstance(Ci.nsIFileInputStream);
  1062.  
  1063.     fileInStream.init(this._file, MODE_RDONLY, PERMS_FILE, false);
  1064.  
  1065.     switch (this._dataType) {
  1066.       case SEARCH_DATA_XML:
  1067.         var domParser = Cc["@mozilla.org/xmlextras/domparser;1"].
  1068.                         createInstance(Ci.nsIDOMParser);
  1069.         var doc = domParser.parseFromStream(fileInStream, "UTF-8",
  1070.                                             this._file.fileSize,
  1071.                                             "text/xml");
  1072.  
  1073.         this._data = doc.documentElement;
  1074.         break;
  1075.       case SEARCH_DATA_TEXT:
  1076.         var binaryInStream = Cc["@mozilla.org/binaryinputstream;1"].
  1077.                              createInstance(Ci.nsIBinaryInputStream);
  1078.         binaryInStream.setInputStream(fileInStream);
  1079.  
  1080.         var bytes = binaryInStream.readByteArray(binaryInStream.available());
  1081.         this._data = bytes;
  1082.  
  1083.         break;
  1084.       default:
  1085.         ERROR("Bogus engine _dataType: \"" + this._dataType + "\"",
  1086.               Cr.NS_ERROR_UNEXPECTED);
  1087.     }
  1088.     fileInStream.close();
  1089.  
  1090.     // Now that the data is loaded, initialize the engine object
  1091.     this._initFromData();
  1092.   },
  1093.  
  1094.   /**
  1095.    * Retrieves the engine data from a URI.
  1096.    */
  1097.   _initFromURI: function SRCH_ENG_initFromURI() {
  1098.     ENSURE_WARN(this._uri instanceof Ci.nsIURI,
  1099.                 "Must have URI when calling _initFromURI!",
  1100.                 Cr.NS_ERROR_UNEXPECTED);
  1101.  
  1102.     LOG("_initFromURI: Downloading engine from: \"" + this._uri.spec + "\".");
  1103.  
  1104.     var chan = gIoSvc.newChannelFromURI(this._uri);
  1105.  
  1106.     if (this._engineToUpdate && (chan instanceof Ci.nsIHttpChannel)) {
  1107.       var lastModified = engineMetadataService.getAttr(this._engineToUpdate,
  1108.                                                        "updatelastmodified");
  1109.       if (lastModified)
  1110.         chan.setRequestHeader("If-Modified-Since", lastModified, false);
  1111.     }
  1112.     var listener = new loadListener(chan, this, this._onLoad);
  1113.     chan.notificationCallbacks = listener;
  1114.     chan.asyncOpen(listener, null);
  1115.   },
  1116.  
  1117.   /**
  1118.    * Attempts to find an EngineURL object in the set of EngineURLs for
  1119.    * this Engine that has the given type string.  (This corresponds to the
  1120.    * "type" attribute in the "Url" node in the OpenSearch spec.)
  1121.    * This method will return the first matching URL object found, or null
  1122.    * if no matching URL is found.
  1123.    *
  1124.    * @param aType string to match the EngineURL's type attribute
  1125.    */
  1126.   _getURLOfType: function SRCH_ENG__getURLOfType(aType) {
  1127.     for (var i = 0; i < this._urls.length; ++i) {
  1128.       if (this._urls[i].type == aType)
  1129.         return this._urls[i];
  1130.     }
  1131.  
  1132.     return null;
  1133.   },
  1134.  
  1135.   _confirmAddEngine: function SRCH_SVC_confirmAddEngine() {
  1136.     var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
  1137.               getService(Ci.nsIStringBundleService);
  1138.     var stringBundle = sbs.createBundle(SEARCH_BUNDLE);
  1139.     var titleMessage = stringBundle.GetStringFromName("addEngineConfirmTitle");
  1140.  
  1141.     // Display only the hostname portion of the URL.
  1142.     var dialogMessage =
  1143.         stringBundle.formatStringFromName("addEngineConfirmation",
  1144.                                           [this._name, this._uri.host], 2);
  1145.     var checkboxMessage = stringBundle.GetStringFromName("addEngineUseNowText");
  1146.     var addButtonLabel =
  1147.         stringBundle.GetStringFromName("addEngineAddButtonLabel");
  1148.  
  1149.     var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  1150.              getService(Ci.nsIPromptService);
  1151.     var buttonFlags = (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0) +
  1152.                       (ps.BUTTON_TITLE_CANCEL    * ps.BUTTON_POS_1) +
  1153.                        ps.BUTTON_POS_0_DEFAULT;
  1154.  
  1155.     var checked = {value: false};
  1156.     // confirmEx returns the index of the button that was pressed.  Since "Add"
  1157.     // is button 0, we want to return the negation of that value.
  1158.     var confirm = !ps.confirmEx(null,
  1159.                                 titleMessage,
  1160.                                 dialogMessage,
  1161.                                 buttonFlags,
  1162.                                 addButtonLabel,
  1163.                                 null, null, // button 1 & 2 names not used
  1164.                                 checkboxMessage,
  1165.                                 checked);
  1166.  
  1167.     return {confirmed: confirm, useNow: checked.value};
  1168.   },
  1169.  
  1170.   /**
  1171.    * Handle the successful download of an engine. Initializes the engine and
  1172.    * triggers parsing of the data. The engine is then flushed to disk. Notifies
  1173.    * the search service once initialization is complete.
  1174.    */
  1175.   _onLoad: function SRCH_ENG_onLoad(aBytes, aEngine) {
  1176.     /**
  1177.      * Handle an error during the load of an engine by prompting the user to
  1178.      * notify him that the load failed.
  1179.      */
  1180.     function onError(aErrorString, aTitleString) {
  1181.       if (aEngine._engineToUpdate) {
  1182.         // We're in an update, so just fail quietly
  1183.         LOG("updating " + aEngine._engineToUpdate.name + " failed");
  1184.         return;
  1185.       }
  1186.       var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
  1187.                 getService(Ci.nsIStringBundleService);
  1188.  
  1189.       var brandBundle = sbs.createBundle(BRAND_BUNDLE);
  1190.       var brandName = brandBundle.GetStringFromName("brandShortName");
  1191.  
  1192.       var searchBundle = sbs.createBundle(SEARCH_BUNDLE);
  1193.       var msgStringName = aErrorString || "error_loading_engine_msg2";
  1194.       var titleStringName = aTitleString || "error_loading_engine_title";
  1195.       var title = searchBundle.GetStringFromName(titleStringName);
  1196.       var text = searchBundle.formatStringFromName(msgStringName,
  1197.                                                    [brandName, aEngine._location],
  1198.                                                    2);
  1199.  
  1200.       var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
  1201.                getService(Ci.nsIWindowWatcher);
  1202.       ww.getNewPrompter(null).alert(title, text);
  1203.     }
  1204.  
  1205.     if (!aBytes) {
  1206.       onError();
  1207.       return;
  1208.     }
  1209.  
  1210.     var engineToUpdate = null;
  1211.     if (aEngine._engineToUpdate) {
  1212.       engineToUpdate = aEngine._engineToUpdate.wrappedJSObject;
  1213.  
  1214.       // Make this new engine use the old engine's file.
  1215.       aEngine._file = engineToUpdate._file;
  1216.     }
  1217.  
  1218.     switch (aEngine._dataType) {
  1219.       case SEARCH_DATA_XML:
  1220.         var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
  1221.                      createInstance(Ci.nsIDOMParser);
  1222.         var doc = parser.parseFromBuffer(aBytes, aBytes.length, "text/xml");
  1223.         aEngine._data = doc.documentElement;
  1224.         break;
  1225.       case SEARCH_DATA_TEXT:
  1226.         aEngine._data = aBytes;
  1227.         break;
  1228.       default:
  1229.         onError();
  1230.         LOG("_onLoad: Bogus engine _dataType: \"" + this._dataType + "\"");
  1231.         return;
  1232.     }
  1233.  
  1234.     try {
  1235.       // Initialize the engine from the obtained data
  1236.       aEngine._initFromData();
  1237.     } catch (ex) {
  1238.       LOG("_onLoad: Failed to init engine!\n" + ex);
  1239.       // Report an error to the user
  1240.       onError();
  1241.       return;
  1242.     }
  1243.  
  1244.     // Check to see if this is a duplicate engine. If we're confirming the
  1245.     // engine load, then we display a "this is a duplicate engine" prompt,
  1246.     // otherwise we fail silently.
  1247.     if (!engineToUpdate) {
  1248.       var ss = Cc["@mozilla.org/browser/search-service;1"].
  1249.                getService(Ci.nsIBrowserSearchService);
  1250.       if (ss.getEngineByName(aEngine.name)) {
  1251.         if (aEngine._confirm)
  1252.           onError("error_duplicate_engine_msg", "error_invalid_engine_title");
  1253.  
  1254.         LOG("_onLoad: duplicate engine found, bailing");
  1255.         return;
  1256.       }
  1257.     }
  1258.  
  1259.     // If requested, confirm the addition now that we have the title.
  1260.     // This property is only ever true for engines added via
  1261.     // nsIBrowserSearchService::addEngine.
  1262.     if (aEngine._confirm) {
  1263.       var confirmation = aEngine._confirmAddEngine();
  1264.       LOG("_onLoad: confirm is " + confirmation.confirmed +
  1265.           "; useNow is " + confirmation.useNow);
  1266.       if (!confirmation.confirmed)
  1267.         return;
  1268.       aEngine._useNow = confirmation.useNow;
  1269.     }
  1270.  
  1271.     // If we don't yet have a file, get one now. The only case where we would
  1272.     // already have a file is if this is an update and _file was set above.
  1273.     if (!aEngine._file)
  1274.       aEngine._file = getSanitizedFile(aEngine.name);
  1275.  
  1276.     if (engineToUpdate) {
  1277.       // Keep track of the last modified date, so that we can make conditional
  1278.       // requests for future updates.
  1279.       engineMetadataService.setAttr(aEngine, "updatelastmodified",
  1280.                                     (new Date()).toUTCString());
  1281.  
  1282.       // If we're updating an app-shipped engine, ensure that the updateURLs
  1283.       // are the same.
  1284.       if (engineToUpdate._isInAppDir) {
  1285.         let oldUpdateURL = engineToUpdate._updateURL;
  1286.         let newUpdateURL = aEngine._updateURL;
  1287.         let oldSelfURL = engineToUpdate._getURLOfType(URLTYPE_OPENSEARCH);
  1288.         if (oldSelfURL && oldSelfURL._hasRelation("self")) {
  1289.           oldUpdateURL = oldSelfURL.template;
  1290.           let newSelfURL = aEngine._getURLOfType(URLTYPE_OPENSEARCH);
  1291.           if (!newSelfURL || !newSelfURL._hasRelation("self")) {
  1292.             LOG("_onLoad: updateURL missing in updated engine for " +
  1293.                 aEngine.name + " aborted");
  1294.             return;
  1295.           }
  1296.           newUpdateURL = newSelfURL.template;
  1297.         }
  1298.  
  1299.         if (oldUpdateURL != newUpdateURL) {
  1300.           LOG("_onLoad: updateURLs do not match! Update of " + aEngine.name + " aborted");
  1301.           return;
  1302.         }
  1303.       }
  1304.  
  1305.       // Set the new engine's icon, if it doesn't yet have one.
  1306.       if (!aEngine._iconURI && engineToUpdate._iconURI)
  1307.         aEngine._iconURI = engineToUpdate._iconURI;
  1308.  
  1309.       // Clear the "use now" flag since we don't want to be changing the
  1310.       // current engine for an update.
  1311.       aEngine._useNow = false;
  1312.     }
  1313.  
  1314.     // Write the engine to file. For readOnly engines, they'll be stored in the
  1315.     // cache following the notification below.
  1316.     if (!aEngine._readOnly)
  1317.       aEngine._serializeToFile();
  1318.  
  1319.     // Notify the search service of the successful load. It will deal with
  1320.     // updates by checking aEngine._engineToUpdate.
  1321.     notifyAction(aEngine, SEARCH_ENGINE_LOADED);
  1322.   },
  1323.  
  1324.   /**
  1325.    * Sets the .iconURI property of the engine.
  1326.    *
  1327.    *  @param aIconURL
  1328.    *         A URI string pointing to the engine's icon. Must have a http[s],
  1329.    *         ftp, or data scheme. Icons with HTTP[S] or FTP schemes will be
  1330.    *         downloaded and converted to data URIs for storage in the engine
  1331.    *         XML files, if the engine is not readonly.
  1332.    *  @param aIsPreferred
  1333.    *         Whether or not this icon is to be preferred. Preferred icons can
  1334.    *         override non-preferred icons.
  1335.    */
  1336.   _setIcon: function SRCH_ENG_setIcon(aIconURL, aIsPreferred) {
  1337.     // If we already have a preferred icon, and this isn't a preferred icon,
  1338.     // just ignore it.
  1339.     if (this._hasPreferredIcon && !aIsPreferred)
  1340.       return;
  1341.  
  1342.     var uri = makeURI(aIconURL);
  1343.  
  1344.     // Ignore bad URIs
  1345.     if (!uri)
  1346.       return;
  1347.  
  1348.     LOG("_setIcon: Setting icon url \"" + uri.spec + "\" for engine \""
  1349.         + this.name + "\".");
  1350.     // Only accept remote icons from http[s] or ftp
  1351.     switch (uri.scheme) {
  1352.       case "data":
  1353.         this._iconURI = uri;
  1354.         notifyAction(this, SEARCH_ENGINE_CHANGED);
  1355.         this._hasPreferredIcon = aIsPreferred;
  1356.         break;
  1357.       case "http":
  1358.       case "https":
  1359.       case "ftp":
  1360.         // No use downloading the icon if the engine file is read-only
  1361.         if (!this._readOnly) {
  1362.           LOG("_setIcon: Downloading icon: \"" + uri.spec +
  1363.               "\" for engine: \"" + this.name + "\"");
  1364.           var chan = gIoSvc.newChannelFromURI(uri);
  1365.  
  1366.           function iconLoadCallback(aByteArray, aEngine) {
  1367.             // This callback may run after we've already set a preferred icon,
  1368.             // so check again.
  1369.             if (aEngine._hasPreferredIcon && !aIsPreferred)
  1370.               return;
  1371.  
  1372.             if (!aByteArray || aByteArray.length > MAX_ICON_SIZE) {
  1373.               LOG("iconLoadCallback: load failed, or the icon was too large!");
  1374.               return;
  1375.             }
  1376.  
  1377.             var str = btoa(String.fromCharCode.apply(null, aByteArray));
  1378.             aEngine._iconURI = makeURI(ICON_DATAURL_PREFIX + str);
  1379.  
  1380.             // The engine might not have a file yet, if it's being downloaded,
  1381.             // because the request for the engine file itself (_onLoad) may not
  1382.             // yet be complete. In that case, this change will be written to
  1383.             // file when _onLoad is called. For readonly engines, we'll store
  1384.             // the changes in the cache once notified below.
  1385.             if (aEngine._file && !aEngine._readOnly)
  1386.               aEngine._serializeToFile();
  1387.  
  1388.             notifyAction(aEngine, SEARCH_ENGINE_CHANGED);
  1389.             aEngine._hasPreferredIcon = aIsPreferred;
  1390.           }
  1391.  
  1392.           // If we're currently acting as an "update engine", then the callback
  1393.           // should set the icon on the engine we're updating and not us, since
  1394.           // |this| might be gone by the time the callback runs.
  1395.           var engineToSet = this._engineToUpdate || this;
  1396.  
  1397.           var listener = new loadListener(chan, engineToSet, iconLoadCallback);
  1398.           chan.notificationCallbacks = listener;
  1399.           chan.asyncOpen(listener, null);
  1400.         }
  1401.         break;
  1402.     }
  1403.   },
  1404.  
  1405.   /**
  1406.    * Initialize this Engine object from the collected data.
  1407.    */
  1408.   _initFromData: function SRCH_ENG_initFromData() {
  1409.  
  1410.     ENSURE_WARN(this._data, "Can't init an engine with no data!",
  1411.                 Cr.NS_ERROR_UNEXPECTED);
  1412.  
  1413.     // Find out what type of engine we are
  1414.     switch (this._dataType) {
  1415.       case SEARCH_DATA_XML:
  1416.         if (checkNameSpace(this._data, [MOZSEARCH_LOCALNAME],
  1417.             [MOZSEARCH_NS_10])) {
  1418.  
  1419.           LOG("_init: Initing MozSearch plugin from " + this._location);
  1420.  
  1421.           this._type = SEARCH_TYPE_MOZSEARCH;
  1422.           this._parseAsMozSearch();
  1423.  
  1424.         } else if (checkNameSpace(this._data, [OPENSEARCH_LOCALNAME],
  1425.                                   OPENSEARCH_NAMESPACES)) {
  1426.  
  1427.           LOG("_init: Initing OpenSearch plugin from " + this._location);
  1428.  
  1429.           this._type = SEARCH_TYPE_OPENSEARCH;
  1430.           this._parseAsOpenSearch();
  1431.  
  1432.         } else
  1433.           FAIL(this._location + " is not a valid search plugin.", Cr.NS_ERROR_FAILURE);
  1434.  
  1435.         break;
  1436.       case SEARCH_DATA_TEXT:
  1437.         LOG("_init: Initing Sherlock plugin from " + this._location);
  1438.  
  1439.         // the only text-based format we support is Sherlock
  1440.         this._type = SEARCH_TYPE_SHERLOCK;
  1441.         this._parseAsSherlock();
  1442.     }
  1443.  
  1444.     // No need to keep a ref to our data (which in some cases can be a document
  1445.     // element) past this point
  1446.     this._data = null;
  1447.   },
  1448.  
  1449.   /**
  1450.    * Initialize this Engine object from a collection of metadata.
  1451.    */
  1452.   _initFromMetadata: function SRCH_ENG_initMetaData(aName, aIconURL, aAlias,
  1453.                                                     aDescription, aMethod,
  1454.                                                     aTemplate) {
  1455.     ENSURE_WARN(!this._readOnly,
  1456.                 "Can't call _initFromMetaData on a readonly engine!",
  1457.                 Cr.NS_ERROR_FAILURE);
  1458.  
  1459.     this._urls.push(new EngineURL("text/html", aMethod, aTemplate));
  1460.  
  1461.     this._name = aName;
  1462.     this.alias = aAlias;
  1463.     this._description = aDescription;
  1464.     this._setIcon(aIconURL, true);
  1465.  
  1466.     this._serializeToFile();
  1467.   },
  1468.  
  1469.   /**
  1470.    * Extracts data from an OpenSearch URL element and creates an EngineURL
  1471.    * object which is then added to the engine's list of URLs.
  1472.    *
  1473.    * @throws NS_ERROR_FAILURE if a URL object could not be created.
  1474.    *
  1475.    * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag.
  1476.    * @see EngineURL()
  1477.    */
  1478.   _parseURL: function SRCH_ENG_parseURL(aElement) {
  1479.     var type     = aElement.getAttribute("type");
  1480.     // According to the spec, method is optional, defaulting to "GET" if not
  1481.     // specified
  1482.     var method   = aElement.getAttribute("method") || "GET";
  1483.     var template = aElement.getAttribute("template");
  1484.  
  1485.     try {
  1486.       var url = new EngineURL(type, method, template);
  1487.     } catch (ex) {
  1488.       FAIL("_parseURL: failed to add " + template + " as a URL",
  1489.            Cr.NS_ERROR_FAILURE);
  1490.     }
  1491.  
  1492.     if (aElement.hasAttribute("rel"))
  1493.       url.rels = aElement.getAttribute("rel").toLowerCase().split(/\s+/);
  1494.  
  1495.     for (var i = 0; i < aElement.childNodes.length; ++i) {
  1496.       var param = aElement.childNodes[i];
  1497.       if (param.localName == "Param") {
  1498.         try {
  1499.           url.addParam(param.getAttribute("name"), param.getAttribute("value"));
  1500.         } catch (ex) {
  1501.           // Ignore failure
  1502.           LOG("_parseURL: Url element has an invalid param");
  1503.         }
  1504.       } else if (param.localName == "MozParam" &&
  1505.                  // We only support MozParams for default search engines
  1506.                  this._isDefault) {
  1507.         var value;
  1508.         switch (param.getAttribute("condition")) {
  1509.           case "defaultEngine":
  1510.             // If this engine was the default search engine, use the true value
  1511.             if (this._isDefaultEngine())
  1512.               value = param.getAttribute("trueValue");
  1513.             else
  1514.               value = param.getAttribute("falseValue");
  1515.             url.addParam(param.getAttribute("name"), value);
  1516.             url._addMozParam({"name": param.getAttribute("name"),
  1517.                               "falseValue": param.getAttribute("falseValue"),
  1518.                               "trueValue": param.getAttribute("trueValue"),
  1519.                               "condition": "defaultEngine"});
  1520.             break;
  1521.  
  1522.           case "pref":
  1523.             try {
  1524.               value = getMozParamPref(param.getAttribute("pref"), value);
  1525.               url.addParam(param.getAttribute("name"), value);
  1526.               url._addMozParam({"pref": param.getAttribute("pref"),
  1527.                                 "name": param.getAttribute("name"),
  1528.                                 "condition": "pref"});
  1529.             } catch (e) { }
  1530.             break;
  1531.         }
  1532.       }
  1533.     }
  1534.  
  1535.     this._urls.push(url);
  1536.   },
  1537.  
  1538.   _isDefaultEngine: function SRCH_ENG__isDefaultEngine() {
  1539.     let defaultPrefB = gPrefSvc.QueryInterface(Ci.nsIPrefService)
  1540.                                .getDefaultBranch(BROWSER_SEARCH_PREF);
  1541.     let nsIPLS = Ci.nsIPrefLocalizedString;
  1542.     let defaultEngine;
  1543.     try {
  1544.       defaultEngine = defaultPrefB.getComplexValue("defaultenginename", nsIPLS).data;
  1545.     } catch (ex) {}
  1546.     return this.name == defaultEngine;
  1547.   },
  1548.  
  1549.   /**
  1550.    * Get the icon from an OpenSearch Image element.
  1551.    * @see http://opensearch.a9.com/spec/1.1/description/#image
  1552.    */
  1553.   _parseImage: function SRCH_ENG_parseImage(aElement) {
  1554.     LOG("_parseImage: Image textContent: \"" + aElement.textContent + "\"");
  1555.     if (aElement.getAttribute("width")  == "16" &&
  1556.         aElement.getAttribute("height") == "16") {
  1557.       this._setIcon(aElement.textContent, true);
  1558.     }
  1559.   },
  1560.  
  1561.   _parseAsMozSearch: function SRCH_ENG_parseAsMoz() {
  1562.     //forward to the OpenSearch parser
  1563.     this._parseAsOpenSearch();
  1564.   },
  1565.  
  1566.   /**
  1567.    * Extract search engine information from the collected data to initialize
  1568.    * the engine object.
  1569.    */
  1570.   _parseAsOpenSearch: function SRCH_ENG_parseAsOS() {
  1571.     var doc = this._data;
  1572.  
  1573.     // The OpenSearch spec sets a default value for the input encoding.
  1574.     this._queryCharset = OS_PARAM_INPUT_ENCODING_DEF;
  1575.  
  1576.     for (var i = 0; i < doc.childNodes.length; ++i) {
  1577.       var child = doc.childNodes[i];
  1578.       switch (child.localName) {
  1579.         case "ShortName":
  1580.           this._name = child.textContent;
  1581.           break;
  1582.         case "Description":
  1583.           this._description = child.textContent;
  1584.           break;
  1585.         case "Url":
  1586.           try {
  1587.             this._parseURL(child);
  1588.           } catch (ex) {
  1589.             // Parsing of the element failed, just skip it.
  1590.           }
  1591.           break;
  1592.         case "Image":
  1593.           this._parseImage(child);
  1594.           break;
  1595.         case "InputEncoding":
  1596.           this._queryCharset = child.textContent.toUpperCase();
  1597.           break;
  1598.  
  1599.         // Non-OpenSearch elements
  1600.         case "SearchForm":
  1601.           this._searchForm = child.textContent;
  1602.           break;
  1603.         case "UpdateUrl":
  1604.           this._updateURL = child.textContent;
  1605.           break;
  1606.         case "UpdateInterval":
  1607.           this._updateInterval = parseInt(child.textContent);
  1608.           break;
  1609.         case "IconUpdateUrl":
  1610.           this._iconUpdateURL = child.textContent;
  1611.           break;
  1612.       }
  1613.     }
  1614.     if (!this.name || (this._urls.length == 0))
  1615.       FAIL("_parseAsOpenSearch: No name, or missing URL!", Cr.NS_ERROR_FAILURE);
  1616.     if (!this.supportsResponseType(URLTYPE_SEARCH_HTML))
  1617.       FAIL("_parseAsOpenSearch: No text/html result type!", Cr.NS_ERROR_FAILURE);
  1618.   },
  1619.  
  1620.   /**
  1621.    * Extract search engine information from the collected data to initialize
  1622.    * the engine object.
  1623.    */
  1624.   _parseAsSherlock: function SRCH_ENG_parseAsSherlock() {
  1625.     /**
  1626.      * Extracts one Sherlock "section" from aSource. A section is essentially
  1627.      * an HTML element with attributes, but each attribute must be on a new
  1628.      * line, by definition.
  1629.      *
  1630.      * @param aLines
  1631.      *        An array of lines from the sherlock file.
  1632.      * @param aSection
  1633.      *        The name of the section (e.g. "search" or "browser"). This value
  1634.      *        is not case sensitive.
  1635.      * @returns an object whose properties correspond to the section's
  1636.      *          attributes.
  1637.      */
  1638.     function getSection(aLines, aSection) {
  1639.       LOG("_parseAsSherlock::getSection: Sherlock lines:\n" +
  1640.           aLines.join("\n"));
  1641.       var lines = aLines;
  1642.       var startMark = new RegExp("^\\s*<" + aSection.toLowerCase() + "\\s*",
  1643.                                  "gi");
  1644.       var endMark   = /\s*>\s*$/gi;
  1645.  
  1646.       var foundStart = false;
  1647.       var startLine, numberOfLines;
  1648.       // Find the beginning and end of the section
  1649.       for (var i = 0; i < lines.length; i++) {
  1650.         if (foundStart) {
  1651.           if (endMark.test(lines[i])) {
  1652.             numberOfLines = i - startLine;
  1653.             // Remove the end marker
  1654.             lines[i] = lines[i].replace(endMark, "");
  1655.             // If the endmarker was not the only thing on the line, include
  1656.             // this line in the results
  1657.             if (lines[i])
  1658.               numberOfLines++;
  1659.             break;
  1660.           }
  1661.         } else {
  1662.           if (startMark.test(lines[i])) {
  1663.             foundStart = true;
  1664.             // Remove the start marker
  1665.             lines[i] = lines[i].replace(startMark, "");
  1666.             startLine = i;
  1667.             // If the line is empty, don't include it in the result
  1668.             if (!lines[i])
  1669.               startLine++;
  1670.           }
  1671.         }
  1672.       }
  1673.       LOG("_parseAsSherlock::getSection: Start index: " + startLine +
  1674.           "\nNumber of lines: " + numberOfLines);
  1675.       lines = lines.splice(startLine, numberOfLines);
  1676.       LOG("_parseAsSherlock::getSection: Section lines:\n" +
  1677.           lines.join("\n"));
  1678.  
  1679.       var section = {};
  1680.       for (var i = 0; i < lines.length; i++) {
  1681.         var line = lines[i].trim();
  1682.  
  1683.         var els = line.split("=");
  1684.         var name = els.shift().trim().toLowerCase();
  1685.         var value = els.join("=").trim();
  1686.  
  1687.         if (!name || !value)
  1688.           continue;
  1689.  
  1690.         // Strip leading and trailing whitespace, remove quotes from the
  1691.         // value, and remove any trailing slashes or ">" characters
  1692.         value = value.replace(/^["']/, "")
  1693.                      .replace(/["']\s*[\\\/]?>?\s*$/, "") || "";
  1694.         value = value.trim();
  1695.  
  1696.         // Don't clobber existing attributes
  1697.         if (!(name in section))
  1698.           section[name] = value;
  1699.       }
  1700.       return section;
  1701.     }
  1702.  
  1703.     /**
  1704.      * Returns an array of name-value pair arrays representing the Sherlock
  1705.      * file's input elements. User defined inputs return USER_DEFINED
  1706.      * as the value. Elements are returned in the order they appear in the
  1707.      * source file.
  1708.      *
  1709.      *   Example:
  1710.      *      <input name="foo" value="bar">
  1711.      *      <input name="foopy" user>
  1712.      *   Returns:
  1713.      *      [["foo", "bar"], ["foopy", "{searchTerms}"]]
  1714.      *
  1715.      * @param aLines
  1716.      *        An array of lines from the source file.
  1717.      */
  1718.     function getInputs(aLines) {
  1719.  
  1720.       /**
  1721.        * Extracts an attribute value from a given a line of text.
  1722.        *    Example: <input value="foo" name="bar">
  1723.        *      Extracts the string |foo| or |bar| given an input aAttr of
  1724.        *      |value| or |name|.
  1725.        * Attributes may be quoted or unquoted. If unquoted, any whitespace
  1726.        * indicates the end of the attribute value.
  1727.        *    Example: < value=22 33 name=44\334 >
  1728.        *      Returns |22| for "value" and |44\334| for "name".
  1729.        *
  1730.        * @param aAttr
  1731.        *        The name of the attribute for which to obtain the value. This
  1732.        *        value is not case sensitive.
  1733.        * @param aLine
  1734.        *        The line containing the attribute.
  1735.        *
  1736.        * @returns the attribute value, or an empty string if the attribute
  1737.        *          doesn't exist.
  1738.        */
  1739.       function getAttr(aAttr, aLine) {
  1740.         // Used to determine whether an "input" line from a Sherlock file is a
  1741.         // "user defined" input.
  1742.         const userInput = /(\s|["'=])user(\s|[>="'\/\\+]|$)/i;
  1743.  
  1744.         LOG("_parseAsSherlock::getAttr: Getting attr: \"" +
  1745.             aAttr + "\" for line: \"" + aLine + "\"");
  1746.         // We're not case sensitive, but we want to return the attribute value
  1747.         // in its original case, so create a copy of the source
  1748.         var lLine = aLine.toLowerCase();
  1749.         var attr = aAttr.toLowerCase();
  1750.  
  1751.         var attrStart = lLine.search(new RegExp("\\s" + attr, "i"));
  1752.         if (attrStart == -1) {
  1753.  
  1754.           // If this is the "user defined input" (i.e. contains the empty
  1755.           // "user" attribute), return our special keyword
  1756.           if (userInput.test(lLine) && attr == "value") {
  1757.             LOG("_parseAsSherlock::getAttr: Found user input!\nLine:\"" + lLine
  1758.                 + "\"");
  1759.             return USER_DEFINED;
  1760.           }
  1761.           // The attribute doesn't exist - ignore
  1762.           LOG("_parseAsSherlock::getAttr: Failed to find attribute:\nLine:\""
  1763.               + lLine + "\"\nAttr:\"" + attr + "\"");
  1764.           return "";
  1765.         }
  1766.  
  1767.         var valueStart = lLine.indexOf("=", attrStart) + "=".length;
  1768.         if (valueStart == -1)
  1769.           return "";
  1770.  
  1771.         var quoteStart = lLine.indexOf("\"", valueStart);
  1772.         if (quoteStart == -1) {
  1773.  
  1774.           // Unquoted attribute, get the rest of the line, trimmed at the first
  1775.           // sign of whitespace. If the rest of the line is only whitespace,
  1776.           // returns a blank string.
  1777.           return lLine.substr(valueStart).replace(/\s.*$/, "");
  1778.  
  1779.         } else {
  1780.           // Make sure that there's only whitespace between the start of the
  1781.           // value and the first quote. If there is, end the attribute value at
  1782.           // the first sign of whitespace. This prevents us from falling into
  1783.           // the next attribute if this is an unquoted attribute followed by a
  1784.           // quoted attribute.
  1785.           var betweenEqualAndQuote = lLine.substring(valueStart, quoteStart);
  1786.           if (/\S/.test(betweenEqualAndQuote))
  1787.             return lLine.substr(valueStart).replace(/\s.*$/, "");
  1788.  
  1789.           // Adjust the start index to account for the opening quote
  1790.           valueStart = quoteStart + "\"".length;
  1791.           // Find the closing quote
  1792.           valueEnd = lLine.indexOf("\"", valueStart);
  1793.           // If there is no closing quote, just go to the end of the line
  1794.           if (valueEnd == -1)
  1795.             valueEnd = aLine.length;
  1796.         }
  1797.         return aLine.substring(valueStart, valueEnd);
  1798.       }
  1799.  
  1800.       var inputs = [];
  1801.  
  1802.       LOG("_parseAsSherlock::getInputs: Lines:\n" + aLines);
  1803.       // Filter out everything but non-inputs
  1804.       lines = aLines.filter(function (line) {
  1805.         return /^\s*<input/i.test(line);
  1806.       });
  1807.       LOG("_parseAsSherlock::getInputs: Filtered lines:\n" + lines);
  1808.  
  1809.       lines.forEach(function (line) {
  1810.         // Strip leading/trailing whitespace and remove the surrounding markup
  1811.         // ("<input" and ">")
  1812.         line = line.trim().replace(/^<input/i, "").replace(/>$/, "");
  1813.  
  1814.         // If this is one of the "directional" inputs (<inputnext>/<inputprev>)
  1815.         const directionalInput = /^(prev|next)/i;
  1816.         if (directionalInput.test(line)) {
  1817.  
  1818.           // Make it look like a normal input by removing "prev" or "next"
  1819.           line = line.replace(directionalInput, "");
  1820.  
  1821.           // If it has a name, give it a dummy value to match previous
  1822.           // nsInternetSearchService behavior
  1823.           if (/name\s*=/i.test(line)) {
  1824.             line += " value=\"0\"";
  1825.           } else
  1826.             return; // Line has no name, skip it
  1827.         }
  1828.  
  1829.         var attrName = getAttr("name", line);
  1830.         var attrValue = getAttr("value", line);
  1831.         LOG("_parseAsSherlock::getInputs: Got input:\nName:\"" + attrName +
  1832.             "\"\nValue:\"" + attrValue + "\"");
  1833.         if (attrValue)
  1834.           inputs.push([attrName, attrValue]);
  1835.       });
  1836.       return inputs;
  1837.     }
  1838.  
  1839.     function err(aErr) {
  1840.       FAIL("_parseAsSherlock::err: Sherlock param error:\n" + aErr,
  1841.            Cr.NS_ERROR_FAILURE);
  1842.     }
  1843.  
  1844.     // First try converting our byte array using the default Sherlock encoding.
  1845.     // If this fails, or if we find a sourceTextEncoding attribute, we need to
  1846.     // reconvert the byte array using the specified encoding.
  1847.     var sherlockLines, searchSection, sourceTextEncoding, browserSection;
  1848.     try {
  1849.       sherlockLines = sherlockBytesToLines(this._data);
  1850.       searchSection = getSection(sherlockLines, "search");
  1851.       browserSection = getSection(sherlockLines, "browser");
  1852.       sourceTextEncoding = parseInt(searchSection["sourcetextencoding"]);
  1853.       if (sourceTextEncoding) {
  1854.         // Re-convert the bytes using the found sourceTextEncoding
  1855.         sherlockLines = sherlockBytesToLines(this._data, sourceTextEncoding);
  1856.         searchSection = getSection(sherlockLines, "search");
  1857.         browserSection = getSection(sherlockLines, "browser");
  1858.       }
  1859.     } catch (ex) {
  1860.       // The conversion using the default charset failed. Remove any non-ascii
  1861.       // bytes and try to find a sourceTextEncoding.
  1862.       var asciiBytes = this._data.filter(function (n) {return !(0x80 & n);});
  1863.       var asciiString = String.fromCharCode.apply(null, asciiBytes);
  1864.       sherlockLines = asciiString.split(NEW_LINES).filter(isUsefulLine);
  1865.       searchSection = getSection(sherlockLines, "search");
  1866.       sourceTextEncoding = parseInt(searchSection["sourcetextencoding"]);
  1867.       if (sourceTextEncoding) {
  1868.         sherlockLines = sherlockBytesToLines(this._data, sourceTextEncoding);
  1869.         searchSection = getSection(sherlockLines, "search");
  1870.         browserSection = getSection(sherlockLines, "browser");
  1871.       } else
  1872.         ERROR("Couldn't find a working charset", Cr.NS_ERROR_FAILURE);
  1873.     }
  1874.  
  1875.     LOG("_parseAsSherlock: Search section:\n" + searchSection.toSource());
  1876.  
  1877.     this._name = searchSection["name"] || err("Missing name!");
  1878.     this._description = searchSection["description"] || "";
  1879.     this._queryCharset = searchSection["querycharset"] ||
  1880.                          queryCharsetFromCode(searchSection["queryencoding"]);
  1881.     this._searchForm = searchSection["searchform"];
  1882.  
  1883.     this._updateInterval = parseInt(browserSection["updatecheckdays"]);
  1884.  
  1885.     this._updateURL = browserSection["update"];
  1886.     this._iconUpdateURL = browserSection["updateicon"];
  1887.  
  1888.     var method = (searchSection["method"] || "GET").toUpperCase();
  1889.     var template = searchSection["action"] || err("Missing action!");
  1890.  
  1891.     var inputs = getInputs(sherlockLines);
  1892.     LOG("_parseAsSherlock: Inputs:\n" + inputs.toSource());
  1893.  
  1894.     var url = null;
  1895.  
  1896.     if (method == "GET") {
  1897.       // Here's how we construct the input string:
  1898.       // <input> is first:  Name Attr:  Prefix      Data           Example:
  1899.       // YES                EMPTY       None        <value>        TEMPLATE<value>
  1900.       // YES                NON-EMPTY   ?           <name>=<value> TEMPLATE?<name>=<value>
  1901.       // NO                 EMPTY       ------------- <ignored> --------------
  1902.       // NO                 NON-EMPTY   &           <name>=<value> TEMPLATE?<n1>=<v1>&<n2>=<v2>
  1903.       for (var i = 0; i < inputs.length; i++) {
  1904.         var name  = inputs[i][0];
  1905.         var value = inputs[i][1];
  1906.         if (i==0) {
  1907.           if (name == "")
  1908.             template += USER_DEFINED;
  1909.           else
  1910.             template += "?" + name + "=" + value;
  1911.         } else if (name != "")
  1912.           template += "&" + name + "=" + value;
  1913.       }
  1914.       url = new EngineURL("text/html", method, template);
  1915.  
  1916.     } else if (method == "POST") {
  1917.       // Create the URL object and just add the parameters directly
  1918.       url = new EngineURL("text/html", method, template);
  1919.       for (var i = 0; i < inputs.length; i++) {
  1920.         var name  = inputs[i][0];
  1921.         var value = inputs[i][1];
  1922.         if (name)
  1923.           url.addParam(name, value);
  1924.       }
  1925.     } else
  1926.       err("Invalid method!");
  1927.  
  1928.     this._urls.push(url);
  1929.   },
  1930.  
  1931.   /**
  1932.    * Init from a JSON record.
  1933.    **/
  1934.   _initWithJSON: function SRCH_ENG__initWithJSON(aJson) {
  1935.     this.__id = aJson._id;
  1936.     this._name = aJson._name;
  1937.     this._description = aJson.description;
  1938.     if (aJson._hasPreferredIcon == undefined)
  1939.       this._hasPreferredIcon = true;
  1940.     else
  1941.       this._hasPreferredIcon = false;
  1942.     this._hidden = aJson.hidden || null;
  1943.     this._type = aJson.type || SEARCH_TYPE_MOZSEARCH;
  1944.     this._queryCharset = aJson.queryCharset || DEFAULT_QUERY_CHARSET;
  1945.     this.__searchForm = aJson.__searchForm;
  1946.     this.__installLocation = aJson._installLocation || SEARCH_APP_DIR;
  1947.     this._updateInterval = aJson._updateInterval || null;
  1948.     this._updateURL = aJson._updateURL || null;
  1949.     this._iconUpdateURL = aJson._iconUpdateURL || null;
  1950.     if (aJson._readOnly == undefined)
  1951.       this._readOnly = true;
  1952.     else
  1953.       this._readOnly = false;
  1954.     this._iconURI = makeURI(aJson._iconURL);
  1955.     for (let i = 0; i < aJson._urls.length; ++i) {
  1956.       let url = aJson._urls[i];
  1957.       let engineURL = new EngineURL(url.type || URLTYPE_SEARCH_HTML,
  1958.                                     url.method || "GET", url.template);
  1959.       engineURL._initWithJSON(url, this);
  1960.       this._urls.push(engineURL);
  1961.     }
  1962.   },
  1963.  
  1964.   /**
  1965.    * Creates a JavaScript object that represents this engine.
  1966.    * @param aFilter
  1967.    *        Whether or not to filter out common default values. Recommended for
  1968.    *        use with _initWithJSON().
  1969.    * @returns An object suitable for serialization as JSON.
  1970.    **/
  1971.   _serializeToJSON: function SRCH_ENG__serializeToJSON(aFilter) {
  1972.     var json = {
  1973.       _id: this._id,
  1974.       _name: this._name,
  1975.       description: this.description,
  1976.       filePath: this._file.QueryInterface(Ci.nsILocalFile).persistentDescriptor,
  1977.       __searchForm: this.__searchForm,
  1978.       _iconURL: this._iconURL,
  1979.       _urls: [url._serializeToJSON() for each(url in this._urls)] 
  1980.     };
  1981.  
  1982.     if (this._installLocation != SEARCH_APP_DIR || !aFilter)
  1983.       json._installLocation = this._installLocation;
  1984.     if (this._updateInterval || !aFilter)
  1985.       json._updateInterval = this._updateInterval;
  1986.     if (this._updateURL || !aFilter)
  1987.       json._updateURL = this._updateURL;
  1988.     if (this._iconUpdateURL || !aFilter)
  1989.       json._iconUpdateURL = this._iconUpdateURL;
  1990.     if (!this._hasPreferredIcon || !aFilter)
  1991.       json._hasPreferredIcon = this._hasPreferredIcon;
  1992.     if (this.hidden || !aFilter)
  1993.       json.hidden = this.hidden;
  1994.     if (this.type != SEARCH_TYPE_MOZSEARCH || !aFilter)
  1995.       json.type = this.type;
  1996.     if (this.queryCharset != DEFAULT_QUERY_CHARSET || !aFilter)
  1997.       json.queryCharset = this.queryCharset;
  1998.     if (this._dataType != SEARCH_DATA_XML || !aFilter)
  1999.       json._dataType = this._dataType;
  2000.     if (!this._readOnly || !aFilter)
  2001.       json._readOnly = this._readOnly;
  2002.  
  2003.     return json;
  2004.   },
  2005.  
  2006.   /**
  2007.    * Returns an XML document object containing the search plugin information,
  2008.    * which can later be used to reload the engine.
  2009.    */
  2010.   _serializeToElement: function SRCH_ENG_serializeToEl() {
  2011.     function appendTextNode(aNameSpace, aLocalName, aValue) {
  2012.       if (!aValue)
  2013.         return null;
  2014.       var node = doc.createElementNS(aNameSpace, aLocalName);
  2015.       node.appendChild(doc.createTextNode(aValue));
  2016.       docElem.appendChild(node);
  2017.       docElem.appendChild(doc.createTextNode("\n"));
  2018.       return node;
  2019.     }
  2020.  
  2021.     var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
  2022.                  createInstance(Ci.nsIDOMParser);
  2023.  
  2024.     var doc = parser.parseFromString(EMPTY_DOC, "text/xml");
  2025.     var docElem = doc.documentElement;
  2026.  
  2027.     docElem.appendChild(doc.createTextNode("\n"));
  2028.  
  2029.     appendTextNode(OPENSEARCH_NS_11, "ShortName", this.name);
  2030.     appendTextNode(OPENSEARCH_NS_11, "Description", this._description);
  2031.     appendTextNode(OPENSEARCH_NS_11, "InputEncoding", this._queryCharset);
  2032.  
  2033.     if (this._iconURI) {
  2034.       var imageNode = appendTextNode(OPENSEARCH_NS_11, "Image",
  2035.                                      this._iconURI.spec);
  2036.       if (imageNode) {
  2037.         imageNode.setAttribute("width", "16");
  2038.         imageNode.setAttribute("height", "16");
  2039.       }
  2040.     }
  2041.  
  2042.     appendTextNode(MOZSEARCH_NS_10, "UpdateInterval", this._updateInterval);
  2043.     appendTextNode(MOZSEARCH_NS_10, "UpdateUrl", this._updateURL);
  2044.     appendTextNode(MOZSEARCH_NS_10, "IconUpdateUrl", this._iconUpdateURL);
  2045.     appendTextNode(MOZSEARCH_NS_10, "SearchForm", this._searchForm);
  2046.  
  2047.     for (var i = 0; i < this._urls.length; ++i)
  2048.       this._urls[i]._serializeToElement(doc, docElem);
  2049.     docElem.appendChild(doc.createTextNode("\n"));
  2050.  
  2051.     return doc;
  2052.   },
  2053.  
  2054.   _lazySerializeToFile: function SRCH_ENG_serializeToFile() {
  2055.     if (this._serializeTimer) {
  2056.       // Reset the timer
  2057.       this._serializeTimer.delay = LAZY_SERIALIZE_DELAY;
  2058.     } else {
  2059.       this._serializeTimer = Cc["@mozilla.org/timer;1"].
  2060.                              createInstance(Ci.nsITimer);
  2061.       var timerCallback = {
  2062.         self: this,
  2063.         notify: function SRCH_ENG_notify(aTimer) {
  2064.           try {
  2065.             this.self._serializeToFile();
  2066.           } catch (ex) {
  2067.             LOG("Serialization from timer callback failed:\n" + ex);
  2068.           }
  2069.           this.self._serializeTimer = null;
  2070.         }
  2071.       };
  2072.       this._serializeTimer.initWithCallback(timerCallback,
  2073.                                             LAZY_SERIALIZE_DELAY,
  2074.                                             Ci.nsITimer.TYPE_ONE_SHOT);
  2075.     }
  2076.   },
  2077.  
  2078.   /**
  2079.    * Serializes the engine object to file.
  2080.    */
  2081.   _serializeToFile: function SRCH_ENG_serializeToFile() {
  2082.     var file = this._file;
  2083.     ENSURE_WARN(!this._readOnly, "Can't serialize a read only engine!",
  2084.                 Cr.NS_ERROR_FAILURE);
  2085.     ENSURE_WARN(file && file.exists(), "Can't serialize: file doesn't exist!",
  2086.                 Cr.NS_ERROR_UNEXPECTED);
  2087.  
  2088.     var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
  2089.               createInstance(Ci.nsIFileOutputStream);
  2090.  
  2091.     // Serialize the engine first - we don't want to overwrite a good file
  2092.     // if this somehow fails.
  2093.     var doc = this._serializeToElement();
  2094.  
  2095.     fos.init(file, (MODE_WRONLY | MODE_TRUNCATE), PERMS_FILE, 0);
  2096.  
  2097.     try {
  2098.       var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
  2099.                        createInstance(Ci.nsIDOMSerializer);
  2100.       serializer.serializeToStream(doc.documentElement, fos, null);
  2101.     } catch (e) {
  2102.       LOG("_serializeToFile: Error serializing engine:\n" + e);
  2103.     }
  2104.  
  2105.     closeSafeOutputStream(fos);
  2106.   },
  2107.  
  2108.   /**
  2109.    * Remove the engine's file from disk. The search service calls this once it
  2110.    * removes the engine from its internal store. This function will throw if
  2111.    * the file cannot be removed.
  2112.    */
  2113.   _remove: function SRCH_ENG_remove() {
  2114.     if (this._readOnly)
  2115.       FAIL("Can't remove read only engine!", Cr.NS_ERROR_FAILURE);
  2116.     if (!this._file || !this._file.exists())
  2117.       FAIL("Can't remove engine: file doesn't exist!", Cr.NS_ERROR_FILE_NOT_FOUND);
  2118.  
  2119.     this._file.remove(false);
  2120.   },
  2121.  
  2122.   // nsISearchEngine
  2123.   get alias() {
  2124.     if (this._alias === null)
  2125.       this._alias = engineMetadataService.getAttr(this, "alias");
  2126.  
  2127.     return this._alias;
  2128.   },
  2129.   set alias(val) {
  2130.     this._alias = val;
  2131.     engineMetadataService.setAttr(this, "alias", val);
  2132.     notifyAction(this, SEARCH_ENGINE_CHANGED);
  2133.   },
  2134.  
  2135.   get description() {
  2136.     return this._description;
  2137.   },
  2138.  
  2139.   get hidden() {
  2140.     if (this._hidden === null)
  2141.       this._hidden = engineMetadataService.getAttr(this, "hidden");
  2142.     return this._hidden;
  2143.   },
  2144.   set hidden(val) {
  2145.     var value = !!val;
  2146.     if (value != this._hidden) {
  2147.       this._hidden = value;
  2148.       engineMetadataService.setAttr(this, "hidden", value);
  2149.       notifyAction(this, SEARCH_ENGINE_CHANGED);
  2150.     }
  2151.   },
  2152.  
  2153.   get iconURI() {
  2154.     return this._iconURI;
  2155.   },
  2156.  
  2157.   get _iconURL() {
  2158.     if (!this._iconURI)
  2159.       return "";
  2160.     return this._iconURI.spec;
  2161.   },
  2162.  
  2163.   // Where the engine is being loaded from: will return the URI's spec if the
  2164.   // engine is being downloaded and does not yet have a file. This is only used
  2165.   // for logging.
  2166.   get _location() {
  2167.     if (this._file)
  2168.       return this._file.path;
  2169.  
  2170.     if (this._uri)
  2171.       return this._uri.spec;
  2172.  
  2173.     return "";
  2174.   },
  2175.  
  2176.   // The file that the plugin is loaded from is a unique identifier for it.  We
  2177.   // use this as the identifier to store data in the sqlite database
  2178.   __id: null,
  2179.   get _id() {
  2180.     if (!this.__id) {
  2181.       ENSURE_WARN(this._file, "No _file for id!", Cr.NS_ERROR_FAILURE);
  2182.   
  2183.       if (this._isInProfile)
  2184.         return this.__id = "[profile]/" + this._file.leafName;
  2185.       if (this._isInAppDir)
  2186.         return this.__id = "[app]/" + this._file.leafName;
  2187.   
  2188.       // We're not in the profile or appdir, so this must be an extension-shipped
  2189.       // plugin. Use the full path.
  2190.       return this.__id = this._file.path;
  2191.     }
  2192.     return this.__id;
  2193.   },
  2194.  
  2195.   get _installLocation() {
  2196.     ENSURE_WARN(this._file && this._file.exists(),
  2197.                 "_installLocation: engine has no file!",
  2198.                 Cr.NS_ERROR_FAILURE);
  2199.  
  2200.     if (this.__installLocation === null) {
  2201.       if (this._file.parent.equals(getDir(NS_APP_SEARCH_DIR)))
  2202.         this.__installLocation = SEARCH_APP_DIR;
  2203.       else if (this._file.parent.equals(getDir(NS_APP_USER_SEARCH_DIR)))
  2204.         this.__installLocation = SEARCH_PROFILE_DIR;
  2205.       else
  2206.         this.__installLocation = SEARCH_IN_EXTENSION;
  2207.     }
  2208.  
  2209.     return this.__installLocation;
  2210.   },
  2211.  
  2212.   get _isInAppDir() {
  2213.     return this._installLocation == SEARCH_APP_DIR;
  2214.   },
  2215.   get _isInProfile() {
  2216.     return this._installLocation == SEARCH_PROFILE_DIR;
  2217.   },
  2218.  
  2219.   get _isDefault() {
  2220.     // For now, our concept of a "default engine" is "one that is not in the
  2221.     // user's profile directory", which is currently equivalent to "is app- or
  2222.     // extension-shipped".
  2223.     return !this._isInProfile;
  2224.   },
  2225.  
  2226.   get _hasUpdates() {
  2227.     // Whether or not the engine has an update URL
  2228.     let selfURL = this._getURLOfType(URLTYPE_OPENSEARCH);
  2229.     return !!(this._updateURL || this._iconUpdateURL || (selfURL &&
  2230.               selfURL._hasRelation("self")));
  2231.   },
  2232.  
  2233.   get name() {
  2234.     return this._name;
  2235.   },
  2236.  
  2237.   get type() {
  2238.     return this._type;
  2239.   },
  2240.  
  2241.   get searchForm() {
  2242.     if (!this._searchForm) {
  2243.       // No searchForm specified in the engine definition file, use the prePath
  2244.       // (e.g. https://foo.com for https://foo.com/search.php?q=bar).
  2245.       var htmlUrl = this._getURLOfType(URLTYPE_SEARCH_HTML);
  2246.       ENSURE_WARN(htmlUrl, "Engine has no HTML URL!", Cr.NS_ERROR_UNEXPECTED);
  2247.       this._searchForm = makeURI(htmlUrl.template).prePath;
  2248.     }
  2249.  
  2250.     return this._searchForm;
  2251.   },
  2252.  
  2253.   get queryCharset() {
  2254.     if (this._queryCharset)
  2255.       return this._queryCharset;
  2256.     return this._queryCharset = queryCharsetFromCode(/* get the default */);
  2257.   },
  2258.  
  2259.   // from nsISearchEngine
  2260.   addParam: function SRCH_ENG_addParam(aName, aValue, aResponseType) {
  2261.     if (!aName || (aValue == null))
  2262.       FAIL("missing name or value for nsISearchEngine::addParam!");
  2263.     ENSURE_WARN(!this._readOnly,
  2264.                 "called nsISearchEngine::addParam on a read-only engine!",
  2265.                 Cr.NS_ERROR_FAILURE);
  2266.     if (!aResponseType)
  2267.       aResponseType = URLTYPE_SEARCH_HTML;
  2268.  
  2269.     var url = this._getURLOfType(aResponseType);
  2270.     if (!url)
  2271.       FAIL("Engine object has no URL for response type " + aResponseType,
  2272.            Cr.NS_ERROR_FAILURE);
  2273.  
  2274.     url.addParam(aName, aValue);
  2275.  
  2276.     // Serialize the changes to file lazily
  2277.     this._lazySerializeToFile();
  2278.   },
  2279.  
  2280.   // from nsISearchEngine
  2281.   getSubmission: function SRCH_ENG_getSubmission(aData, aResponseType) {
  2282.     if (!aResponseType)
  2283.       aResponseType = URLTYPE_SEARCH_HTML;
  2284.  
  2285.     // Check for updates on the first use of an app-shipped engine
  2286.     if (this._isInAppDir && aResponseType == URLTYPE_SEARCH_HTML && !this._used) {
  2287.       this._used = true;
  2288.       engineUpdateService.update(this);
  2289.     }
  2290.  
  2291.     var url = this._getURLOfType(aResponseType);
  2292.  
  2293.     if (!url)
  2294.       return null;
  2295.  
  2296.     if (!aData) {
  2297.       // Return a dummy submission object with our searchForm attribute
  2298.       return new Submission(makeURI(this.searchForm), null);
  2299.     }
  2300.  
  2301.     LOG("getSubmission: In data: \"" + aData + "\"");
  2302.     var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
  2303.                        getService(Ci.nsITextToSubURI);
  2304.     var data = "";
  2305.     try {
  2306.       data = textToSubURI.ConvertAndEscape(this.queryCharset, aData);
  2307.     } catch (ex) {
  2308.       LOG("getSubmission: Falling back to default queryCharset!");
  2309.       data = textToSubURI.ConvertAndEscape(DEFAULT_QUERY_CHARSET, aData);
  2310.     }
  2311.     LOG("getSubmission: Out data: \"" + data + "\"");
  2312.     return url.getSubmission(data, this);
  2313.   },
  2314.  
  2315.   // from nsISearchEngine
  2316.   supportsResponseType: function SRCH_ENG_supportsResponseType(type) {
  2317.     return (this._getURLOfType(type) != null);
  2318.   },
  2319.  
  2320.   // nsISupports
  2321.   QueryInterface: function SRCH_ENG_QI(aIID) {
  2322.     if (aIID.equals(Ci.nsISearchEngine) ||
  2323.         aIID.equals(Ci.nsISupports))
  2324.       return this;
  2325.     throw Cr.NS_ERROR_NO_INTERFACE;
  2326.   },
  2327.  
  2328.   get wrappedJSObject() {
  2329.     return this;
  2330.   }
  2331.  
  2332. };
  2333.  
  2334. // nsISearchSubmission
  2335. function Submission(aURI, aPostData) {
  2336.   this._uri = aURI;
  2337.   this._postData = aPostData;
  2338. }
  2339. Submission.prototype = {
  2340.   get uri() {
  2341.     return this._uri;
  2342.   },
  2343.   get postData() {
  2344.     return this._postData;
  2345.   },
  2346.   QueryInterface: function SRCH_SUBM_QI(aIID) {
  2347.     if (aIID.equals(Ci.nsISearchSubmission) ||
  2348.         aIID.equals(Ci.nsISupports))
  2349.       return this;
  2350.     throw Cr.NS_ERROR_NO_INTERFACE;
  2351.   }
  2352. }
  2353.  
  2354. // nsIBrowserSearchService
  2355. function SearchService() {
  2356.   this._init();
  2357. }
  2358. SearchService.prototype = {
  2359.   _engines: { },
  2360.   _sortedEngines: null,
  2361.   // Whether or not we need to write the order of engines on shutdown. This
  2362.   // needs to happen anytime _sortedEngines is modified after initial startup. 
  2363.   _needToSetOrderPrefs: false,
  2364.  
  2365.   _init: function() {
  2366.     // Replace empty LOG function with the useful one if the log pref is set.
  2367.     if (getBoolPref(BROWSER_SEARCH_PREF + "log", false))
  2368.       LOG = DO_LOG;
  2369.  
  2370.     engineMetadataService.init();
  2371.     engineUpdateService.init();
  2372.  
  2373.     this._loadEngines();
  2374.     this._addObservers();
  2375.  
  2376.     // Now that all engines are loaded, build the sorted engine list
  2377.     this._buildSortedEngineList();
  2378.  
  2379.     let selectedEngineName = getLocalizedPref(BROWSER_SEARCH_PREF +
  2380.                                               "selectedEngine");
  2381.     this._currentEngine = this.getEngineByName(selectedEngineName) ||
  2382.                           this.defaultEngine;
  2383.   },
  2384.  
  2385.   _buildCache: function SRCH_SVC__buildCache() {
  2386.     if (!getBoolPref(BROWSER_SEARCH_PREF + "cache.enabled", true))
  2387.       return;
  2388.  
  2389.     let cache = {};
  2390.     let locale = getLocale();
  2391.     let buildID = Cc["@mozilla.org/xre/app-info;1"].
  2392.                   getService(Ci.nsIXULAppInfo).platformBuildID;
  2393.  
  2394.     // Allows us to force a cache refresh should the cache format change.
  2395.     cache.version = CACHE_VERSION;
  2396.     // We don't want to incur the costs of stat()ing each plugin on every
  2397.     // startup when the only (supported) time they will change is during
  2398.     // runtime (where we refresh for changes through the API) and app updates
  2399.     // (where the buildID is obviously going to change).
  2400.     // Extension-shipped plugins are the only exception to this, but their
  2401.     // directories are blown away during updates, so we'll detect their changes.
  2402.     cache.buildID = buildID;
  2403.     cache.locale = locale;
  2404.  
  2405.     cache.directories = {};
  2406.  
  2407.     for each (let engine in this._engines) {
  2408.       let parent = engine._file.parent;
  2409.       if (!cache.directories[parent.path]) {
  2410.         let cacheEntry = {};
  2411.         cacheEntry.lastModifiedTime = parent.lastModifiedTime;
  2412.         cacheEntry.engines = [];
  2413.         cache.directories[parent.path] = cacheEntry;
  2414.       }
  2415.       cache.directories[parent.path].engines.push(engine._serializeToJSON(true));
  2416.     }
  2417.  
  2418.     let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
  2419.     let stream = Cc["@mozilla.org/network/file-output-stream;1"].
  2420.                  createInstance(Ci.nsIFileOutputStream);
  2421.     let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
  2422.                     createInstance(Ci.nsIConverterOutputStream);
  2423.     let cacheFile = getDir(NS_APP_USER_PROFILE_50_DIR);
  2424.     cacheFile.append("search.json");
  2425.  
  2426.     try {
  2427.       LOG("_buildCache: Writing to cache file.");
  2428.       stream.init(cacheFile, (MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE), PERMS_FILE, 0);
  2429.       converter.init(stream, "UTF-8", 0, 0x0000);
  2430.       converter.writeString(json.encode(cache));
  2431.     } catch (ex) {
  2432.       LOG("_buildCache: Could not write to cache file: " + ex);
  2433.     } finally {
  2434.       converter.close();
  2435.       stream.close();
  2436.     }
  2437.   },
  2438.  
  2439.   _loadEngines: function SRCH_SVC__loadEngines() {
  2440.     // See if we have a cache file so we don't have to parse a bunch of XML.
  2441.     let cache = {};
  2442.     let cacheEnabled = getBoolPref(BROWSER_SEARCH_PREF + "cache.enabled", true);
  2443.     if (cacheEnabled) {
  2444.       let cacheFile = getDir(NS_APP_USER_PROFILE_50_DIR);
  2445.       cacheFile.append("search.json");
  2446.       if (cacheFile.exists())
  2447.         cache = this._readCacheFile(cacheFile);
  2448.     }
  2449.  
  2450.     let loadDirs = [];
  2451.     let locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
  2452.     while (locations.hasMoreElements()) {
  2453.       let dir = locations.getNext().QueryInterface(Ci.nsIFile);
  2454.       if (dir.directoryEntries.hasMoreElements())
  2455.         loadDirs.push(dir);
  2456.     }
  2457.  
  2458.     function modifiedDir(aDir) {
  2459.       return (!cache.directories[aDir.path] ||
  2460.               cache.directories[aDir.path].lastModifiedTime != aDir.lastModifiedTime);
  2461.     }
  2462.  
  2463.     function notInLoadDirs(aCachePath, aIndex)
  2464.       aCachePath != loadDirs[aIndex].path;
  2465.  
  2466.     let buildID = Cc["@mozilla.org/xre/app-info;1"].
  2467.                   getService(Ci.nsIXULAppInfo).platformBuildID;
  2468.     let cachePaths = [path for (path in cache.directories)];
  2469.  
  2470.     let rebuildCache = !cache.directories ||
  2471.                        cache.version != CACHE_VERSION ||
  2472.                        cache.locale != getLocale() ||
  2473.                        cache.buildID != buildID ||
  2474.                        cachePaths.length != loadDirs.length ||
  2475.                        cachePaths.some(notInLoadDirs) ||
  2476.                        loadDirs.some(modifiedDir);
  2477.  
  2478.     if (!cacheEnabled || rebuildCache) {
  2479.       LOG("_loadEngines: Absent or outdated cache. Loading engines from disk.");
  2480.       loadDirs.forEach(this._loadEnginesFromDir, this);
  2481.  
  2482.       if (cacheEnabled)
  2483.         this._buildCache();
  2484.       return;
  2485.     }
  2486.  
  2487.     for each (let dir in cache.directories)
  2488.       this._loadEnginesFromCache(dir);
  2489.   },
  2490.  
  2491.   _readCacheFile: function SRCH_SVC__readCacheFile(aFile) {
  2492.     let stream = Cc["@mozilla.org/network/file-input-stream;1"].
  2493.                  createInstance(Ci.nsIFileInputStream);
  2494.     let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
  2495.  
  2496.     try {
  2497.       stream.init(aFile, MODE_RDONLY, PERMS_FILE, 0);
  2498.       return json.decodeFromStream(stream, stream.available());
  2499.     } catch(ex) {
  2500.       LOG("_readCacheFile: Error reading cache file: " + ex);
  2501.     } finally {
  2502.       stream.close();
  2503.     }
  2504.     return false;
  2505.   },
  2506.  
  2507.   _batchTimer: null,
  2508.   _batchCacheInvalidation: function SRCH_SVC__batchCacheInvalidation() {
  2509.     let callback = {
  2510.       self: this,
  2511.       notify: function SRCH_SVC_batchTimerNotify(aTimer) {
  2512.         LOG("_batchCacheInvalidation: Invalidating engine cache");
  2513.         this.self._buildCache();
  2514.         this.self._batchTimer = null;
  2515.       }
  2516.     };
  2517.  
  2518.     if (!this._batchTimer) {
  2519.       this._batchTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  2520.       this._batchTimer.initWithCallback(callback, CACHE_INVALIDATION_DELAY,
  2521.                                         Ci.nsITimer.TYPE_ONE_SHOT);
  2522.     } else {
  2523.       this._batchTimer.delay = CACHE_INVALIDATION_DELAY;
  2524.       LOG("_batchCacheInvalidation: Batch timer reset");
  2525.     }
  2526.   },
  2527.  
  2528.   _addEngineToStore: function SRCH_SVC_addEngineToStore(aEngine) {
  2529.     LOG("_addEngineToStore: Adding engine: \"" + aEngine.name + "\"");
  2530.  
  2531.     // See if there is an existing engine with the same name. However, if this
  2532.     // engine is updating another engine, it's allowed to have the same name.
  2533.     var hasSameNameAsUpdate = (aEngine._engineToUpdate &&
  2534.                                aEngine.name == aEngine._engineToUpdate.name);
  2535.     if (aEngine.name in this._engines && !hasSameNameAsUpdate) {
  2536.       LOG("_addEngineToStore: Duplicate engine found, aborting!");
  2537.       return;
  2538.     }
  2539.  
  2540.     if (aEngine._engineToUpdate) {
  2541.       // We need to replace engineToUpdate with the engine that just loaded.
  2542.       var oldEngine = aEngine._engineToUpdate;
  2543.  
  2544.       // Remove the old engine from the hash, since it's keyed by name, and our
  2545.       // name might change (the update might have a new name).
  2546.       delete this._engines[oldEngine.name];
  2547.  
  2548.       // Hack: we want to replace the old engine with the new one, but since
  2549.       // people may be holding refs to the nsISearchEngine objects themselves,
  2550.       // we'll just copy over all "private" properties (those without a getter
  2551.       // or setter) from one object to the other.
  2552.       for (var p in aEngine) {
  2553.         if (!(aEngine.__lookupGetter__(p) || aEngine.__lookupSetter__(p)))
  2554.           oldEngine[p] = aEngine[p];
  2555.       }
  2556.       aEngine = oldEngine;
  2557.       aEngine._engineToUpdate = null;
  2558.  
  2559.       // Add the engine back
  2560.       this._engines[aEngine.name] = aEngine;
  2561.       notifyAction(aEngine, SEARCH_ENGINE_CHANGED);
  2562.     } else {
  2563.       // Not an update, just add the new engine.
  2564.       this._engines[aEngine.name] = aEngine;
  2565.       // Only add the engine to the list of sorted engines if the initial list
  2566.       // has already been built (i.e. if this._sortedEngines is non-null). If
  2567.       // it hasn't, we're still loading engines from disk, and will build the
  2568.       // sorted engine list when that initial loading is done.
  2569.       if (this._sortedEngines) {
  2570.         this._sortedEngines.push(aEngine);
  2571.         this._needToSetOrderPrefs = true;
  2572.       }
  2573.       notifyAction(aEngine, SEARCH_ENGINE_ADDED);
  2574.     }
  2575.  
  2576.     if (aEngine._hasUpdates) {
  2577.       // Schedule the engine's next update, if it isn't already.
  2578.       if (!engineMetadataService.getAttr(aEngine, "updateexpir"))
  2579.         engineUpdateService.scheduleNextUpdate(aEngine);
  2580.   
  2581.       // We need to save the engine's _dataType, if this is the first time the
  2582.       // engine is added to the dataStore, since ._dataType isn't persisted
  2583.       // and will change on the next startup (since the engine will then be
  2584.       // XML). We need this so that we know how to load any future updates from
  2585.       // this engine.
  2586.       if (!engineMetadataService.getAttr(aEngine, "updatedatatype"))
  2587.         engineMetadataService.setAttr(aEngine, "updatedatatype",
  2588.                                       aEngine._dataType);
  2589.     }
  2590.   },
  2591.  
  2592.   _loadEnginesFromCache: function SRCH_SVC__loadEnginesFromCache(aDir) {
  2593.     let engines = aDir.engines;
  2594.     LOG("_loadEnginesFromCache: Loading from cache. " + engines.length + " engines to load.");
  2595.     for (let i = 0; i < engines.length; i++) {
  2596.       let json = engines[i];
  2597.       let engine = new Engine({cached: true, value: json.filePath}, json._dataType,
  2598.                               json._readOnly);
  2599.       engine._initWithJSON(json);
  2600.       this._addEngineToStore(engine);
  2601.     }
  2602.   },
  2603.  
  2604.   _loadEnginesFromDir: function SRCH_SVC__loadEnginesFromDir(aDir) {
  2605.     LOG("_loadEnginesFromDir: Searching in " + aDir.path + " for search engines.");
  2606.  
  2607.     // Check whether aDir is the user profile dir
  2608.     var isInProfile = aDir.equals(getDir(NS_APP_USER_SEARCH_DIR));
  2609.  
  2610.     var files = aDir.directoryEntries
  2611.                     .QueryInterface(Ci.nsIDirectoryEnumerator);
  2612.  
  2613.     var addedEngines = [];
  2614.     while (files.hasMoreElements()) {
  2615.       var file = files.nextFile;
  2616.  
  2617.       // Ignore hidden and empty files, and directories
  2618.       if (!file.isFile() || file.fileSize == 0 || file.isHidden())
  2619.         continue;
  2620.  
  2621.       var fileURL = gIoSvc.newFileURI(file).QueryInterface(Ci.nsIURL);
  2622.       var fileExtension = fileURL.fileExtension.toLowerCase();
  2623.       var isWritable = isInProfile && file.isWritable();
  2624.  
  2625.       var dataType;
  2626.       switch (fileExtension) {
  2627.         case XML_FILE_EXT:
  2628.           dataType = SEARCH_DATA_XML;
  2629.           break;
  2630.         case SHERLOCK_FILE_EXT:
  2631.           dataType = SEARCH_DATA_TEXT;
  2632.           break;
  2633.         default:
  2634.           // Not an engine
  2635.           continue;
  2636.       }
  2637.  
  2638.       var addedEngine = null;
  2639.       try {
  2640.         addedEngine = new Engine(file, dataType, !isWritable);
  2641.         addedEngine._initFromFile();
  2642.         if (addedEngine._used)
  2643.           addedEngine._used = false;
  2644.       } catch (ex) {
  2645.         LOG("_loadEnginesFromDir: Failed to load " + file.path + "!\n" + ex);
  2646.         continue;
  2647.       }
  2648.  
  2649.       if (fileExtension == SHERLOCK_FILE_EXT) {
  2650.         if (isWritable) {
  2651.           try {
  2652.             this._convertSherlockFile(addedEngine, fileURL.fileBaseName);
  2653.           } catch (ex) {
  2654.             LOG("_loadEnginesFromDir: Failed to convert: " + fileURL.path + "\n" + ex);
  2655.             // The engine couldn't be converted, mark it as read-only
  2656.             addedEngine._readOnly = true;
  2657.           }
  2658.         }
  2659.  
  2660.         // If the engine still doesn't have an icon, see if we can find one
  2661.         if (!addedEngine._iconURI) {
  2662.           var icon = this._findSherlockIcon(file, fileURL.fileBaseName);
  2663.           if (icon)
  2664.             addedEngine._iconURI = gIoSvc.newFileURI(icon);
  2665.         }
  2666.       }
  2667.  
  2668.       this._addEngineToStore(addedEngine);
  2669.       addedEngines.push(addedEngine);
  2670.     }
  2671.     return addedEngines;
  2672.   },
  2673.  
  2674.   _saveSortedEngineList: function SRCH_SVC_saveSortedEngineList() {
  2675.     // We only need to write the prefs. if something has changed.
  2676.     if (!this._needToSetOrderPrefs)
  2677.       return;
  2678.  
  2679.     // Set the useDB pref to indicate that from now on we should use the order
  2680.     // information stored in the database.
  2681.     gPrefSvc.setBoolPref(BROWSER_SEARCH_PREF + "useDBForOrder", true);
  2682.  
  2683.     var engines = this._getSortedEngines(true);
  2684.     var values = [];
  2685.     var names = [];
  2686.  
  2687.     for (var i = 0; i < engines.length; ++i) {
  2688.       names[i] = "order";
  2689.       values[i] = i + 1;
  2690.     }
  2691.  
  2692.     engineMetadataService.setAttrs(engines, names, values);
  2693.   },
  2694.  
  2695.   _buildSortedEngineList: function SRCH_SVC_buildSortedEngineList() {
  2696.     var addedEngines = { };
  2697.     this._sortedEngines = [];
  2698.     var engine;
  2699.  
  2700.     // If the user has specified a custom engine order, read the order
  2701.     // information from the engineMetadataService instead of the default
  2702.     // prefs.
  2703.     if (getBoolPref(BROWSER_SEARCH_PREF + "useDBForOrder", false)) {
  2704.       for each (engine in this._engines) {
  2705.         var orderNumber = engineMetadataService.getAttr(engine, "order");
  2706.  
  2707.         // Since the DB isn't regularly cleared, and engine files may disappear
  2708.         // without us knowing, we may already have an engine in this slot. If
  2709.         // that happens, we just skip it - it will be added later on as an
  2710.         // unsorted engine. This problem will sort itself out when we call
  2711.         // _saveSortedEngineList at shutdown.
  2712.         if (orderNumber && !this._sortedEngines[orderNumber-1]) {
  2713.           this._sortedEngines[orderNumber-1] = engine;
  2714.           addedEngines[engine.name] = engine;
  2715.         } else {
  2716.           // We need to call _saveSortedEngines so this gets sorted out.
  2717.           this._needToSetOrderPrefs = true;
  2718.         }
  2719.       }
  2720.  
  2721.       // Filter out any nulls for engines that may have been removed
  2722.       var filteredEngines = this._sortedEngines.filter(function(a) { return !!a; });
  2723.       if (this._sortedEngines.length != filteredEngines.length)
  2724.         this._needToSetOrderPrefs = true;
  2725.       this._sortedEngines = filteredEngines;
  2726.  
  2727.     } else {
  2728.       // The DB isn't being used, so just read the engine order from the prefs
  2729.       var i = 0;
  2730.       var engineName;
  2731.       var prefName;
  2732.  
  2733.       try {
  2734.         var extras =
  2735.           gPrefSvc.getChildList(BROWSER_SEARCH_PREF + "order.extra.", { });
  2736.  
  2737.         for each (prefName in extras) {
  2738.           engineName = gPrefSvc.getCharPref(prefName);
  2739.  
  2740.           engine = this._engines[engineName];
  2741.           if (!engine || engine.name in addedEngines)
  2742.             continue;
  2743.  
  2744.           this._sortedEngines.push(engine);
  2745.           addedEngines[engine.name] = engine;
  2746.         }
  2747.       }
  2748.       catch (e) { }
  2749.  
  2750.       while (true) {
  2751.         engineName = getLocalizedPref(BROWSER_SEARCH_PREF + "order." + (++i));
  2752.         if (!engineName)
  2753.           break;
  2754.  
  2755.         engine = this._engines[engineName];
  2756.         if (!engine || engine.name in addedEngines)
  2757.           continue;
  2758.         
  2759.         this._sortedEngines.push(engine);
  2760.         addedEngines[engine.name] = engine;
  2761.       }
  2762.     }
  2763.  
  2764.     // Array for the remaining engines, alphabetically sorted
  2765.     var alphaEngines = [];
  2766.  
  2767.     for each (engine in this._engines) {
  2768.       if (!(engine.name in addedEngines))
  2769.         alphaEngines.push(this._engines[engine.name]);
  2770.     }
  2771.     alphaEngines = alphaEngines.sort(function (a, b) {
  2772.                                        return a.name.localeCompare(b.name);
  2773.                                      });
  2774.     this._sortedEngines = this._sortedEngines.concat(alphaEngines);
  2775.   },
  2776.  
  2777.   /**
  2778.    * Converts a Sherlock file and its icon into the custom XML format used by
  2779.    * the Search Service. Saves the engine's icon (if present) into the XML as a
  2780.    * data: URI and changes the extension of the source file from ".src" to
  2781.    * ".xml". The engine data is then written to the file as XML.
  2782.    * @param aEngine
  2783.    *        The Engine object that needs to be converted.
  2784.    * @param aBaseName
  2785.    *        The basename of the Sherlock file.
  2786.    *          Example: "foo" for file "foo.src".
  2787.    *
  2788.    * @throws NS_ERROR_FAILURE if the file could not be converted.
  2789.    *
  2790.    * @see nsIURL::fileBaseName
  2791.    */
  2792.   _convertSherlockFile: function SRCH_SVC_convertSherlock(aEngine, aBaseName) {
  2793.     var oldSherlockFile = aEngine._file;
  2794.  
  2795.     // Back up the old file
  2796.     try {
  2797.       var backupDir = oldSherlockFile.parent;
  2798.       backupDir.append("searchplugins-backup");
  2799.  
  2800.       if (!backupDir.exists())
  2801.         backupDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  2802.  
  2803.       oldSherlockFile.copyTo(backupDir, null);
  2804.     } catch (ex) {
  2805.       // Just bail. Engines that can't be backed up won't be converted, but
  2806.       // engines that aren't converted are loaded as readonly.
  2807.       FAIL("_convertSherlockFile: Couldn't back up " + oldSherlockFile.path +
  2808.            ":\n" + ex, Cr.NS_ERROR_FAILURE);
  2809.     }
  2810.  
  2811.     // Rename the file, but don't clobber existing files
  2812.     var newXMLFile = oldSherlockFile.parent.clone();
  2813.     newXMLFile.append(aBaseName + "." + XML_FILE_EXT);
  2814.  
  2815.     if (newXMLFile.exists()) {
  2816.       // There is an existing file with this name, create a unique file
  2817.       newXMLFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
  2818.     }
  2819.  
  2820.     // Rename the .src file to .xml
  2821.     oldSherlockFile.moveTo(null, newXMLFile.leafName);
  2822.  
  2823.     aEngine._file = newXMLFile;
  2824.  
  2825.     // Write the converted engine to disk
  2826.     aEngine._serializeToFile();
  2827.  
  2828.     // Update the engine's _type.
  2829.     aEngine._type = SEARCH_TYPE_MOZSEARCH;
  2830.  
  2831.     // See if it has a corresponding icon
  2832.     try {
  2833.       var icon = this._findSherlockIcon(aEngine._file, aBaseName);
  2834.       if (icon && icon.fileSize < MAX_ICON_SIZE) {
  2835.         // Use this as the engine's icon
  2836.         var bStream = Cc["@mozilla.org/binaryinputstream;1"].
  2837.                         createInstance(Ci.nsIBinaryInputStream);
  2838.         var fileInStream = Cc["@mozilla.org/network/file-input-stream;1"].
  2839.                            createInstance(Ci.nsIFileInputStream);
  2840.  
  2841.         fileInStream.init(icon, MODE_RDONLY, PERMS_FILE, 0);
  2842.         bStream.setInputStream(fileInStream);
  2843.  
  2844.         var bytes = [];
  2845.         while (bStream.available() != 0)
  2846.           bytes = bytes.concat(bStream.readByteArray(bStream.available()));
  2847.         bStream.close();
  2848.  
  2849.         // Convert the byte array to a base64-encoded string
  2850.         var str = btoa(String.fromCharCode.apply(null, bytes));
  2851.  
  2852.         aEngine._iconURI = makeURI(ICON_DATAURL_PREFIX + str);
  2853.         LOG("_importSherlockEngine: Set sherlock iconURI to: \"" +
  2854.             aEngine._iconURL + "\"");
  2855.  
  2856.         // Write the engine to disk to save changes
  2857.         aEngine._serializeToFile();
  2858.  
  2859.         // Delete the icon now that we're sure everything's been saved
  2860.         icon.remove(false);
  2861.       }
  2862.     } catch (ex) { LOG("_convertSherlockFile: Error setting icon:\n" + ex); }
  2863.   },
  2864.  
  2865.   /**
  2866.    * Finds an icon associated to a given Sherlock file. Searches the provided
  2867.    * file's parent directory looking for files with the same base name and one
  2868.    * of the file extensions in SHERLOCK_ICON_EXTENSIONS.
  2869.    * @param aEngineFile
  2870.    *        The Sherlock plugin file.
  2871.    * @param aBaseName
  2872.    *        The basename of the Sherlock file.
  2873.    *          Example: "foo" for file "foo.src".
  2874.    * @see nsIURL::fileBaseName
  2875.    */
  2876.   _findSherlockIcon: function SRCH_SVC_findSherlock(aEngineFile, aBaseName) {
  2877.     for (var i = 0; i < SHERLOCK_ICON_EXTENSIONS.length; i++) {
  2878.       var icon = aEngineFile.parent.clone();
  2879.       icon.append(aBaseName + SHERLOCK_ICON_EXTENSIONS[i]);
  2880.       if (icon.exists() && icon.isFile())
  2881.         return icon;
  2882.     }
  2883.     return null;
  2884.   },
  2885.  
  2886.   /**
  2887.    * Get a sorted array of engines.
  2888.    * @param aWithHidden
  2889.    *        True if hidden plugins should be included in the result.
  2890.    */
  2891.   _getSortedEngines: function SRCH_SVC_getSorted(aWithHidden) {
  2892.     if (aWithHidden)
  2893.       return this._sortedEngines;
  2894.  
  2895.     return this._sortedEngines.filter(function (engine) {
  2896.                                         return !engine.hidden;
  2897.                                       });
  2898.   },
  2899.  
  2900.   // nsIBrowserSearchService
  2901.   getEngines: function SRCH_SVC_getEngines(aCount) {
  2902.     LOG("getEngines: getting all engines");
  2903.     var engines = this._getSortedEngines(true);
  2904.     aCount.value = engines.length;
  2905.     return engines;
  2906.   },
  2907.  
  2908.   getVisibleEngines: function SRCH_SVC_getVisible(aCount) {
  2909.     LOG("getVisibleEngines: getting all visible engines");
  2910.     var engines = this._getSortedEngines(false);
  2911.     aCount.value = engines.length;
  2912.     return engines;
  2913.   },
  2914.  
  2915.   getDefaultEngines: function SRCH_SVC_getDefault(aCount) {
  2916.     function isDefault(engine) {
  2917.       return engine._isDefault;
  2918.     };
  2919.     var engines = this._sortedEngines.filter(isDefault);
  2920.     var engineOrder = {};
  2921.     var engineName;
  2922.     var i = 1;
  2923.  
  2924.     // Build a list of engines which we have ordering information for.
  2925.     // We're rebuilding the list here because _sortedEngines contain the
  2926.     // current order, but we want the original order.
  2927.  
  2928.     // First, look at the "browser.search.order.extra" branch.
  2929.     try {
  2930.       var extras = gPrefSvc.getChildList(BROWSER_SEARCH_PREF + "order.extra.",
  2931.                                          {});
  2932.  
  2933.       for each (var prefName in extras) {
  2934.         engineName = gPrefSvc.getCharPref(prefName);
  2935.  
  2936.         if (!(engineName in engineOrder))
  2937.           engineOrder[engineName] = i++;
  2938.       }
  2939.     } catch (e) {
  2940.       LOG("Getting extra order prefs failed: " + e);
  2941.     }
  2942.  
  2943.     // Now look through the "browser.search.order" branch.
  2944.     for (var j = 1; ; j++) {
  2945.       engineName = getLocalizedPref(BROWSER_SEARCH_PREF + "order." + j);
  2946.       if (!engineName)
  2947.         break;
  2948.  
  2949.       if (!(engineName in engineOrder))
  2950.         engineOrder[engineName] = i++;
  2951.     }
  2952.  
  2953.     LOG("getDefaultEngines: engineOrder: " + engineOrder.toSource());
  2954.  
  2955.     function compareEngines (a, b) {
  2956.       var aIdx = engineOrder[a.name];
  2957.       var bIdx = engineOrder[b.name];
  2958.  
  2959.       if (aIdx && bIdx)
  2960.         return aIdx - bIdx;
  2961.       if (aIdx)
  2962.         return -1;
  2963.       if (bIdx)
  2964.         return 1;
  2965.  
  2966.       return a.name.localeCompare(b.name);
  2967.     }
  2968.     engines.sort(compareEngines);
  2969.  
  2970.     aCount.value = engines.length;
  2971.     return engines;
  2972.   },
  2973.  
  2974.   getEngineByName: function SRCH_SVC_getEngineByName(aEngineName) {
  2975.     return this._engines[aEngineName] || null;
  2976.   },
  2977.  
  2978.   getEngineByAlias: function SRCH_SVC_getEngineByAlias(aAlias) {
  2979.     for (var engineName in this._engines) {
  2980.       var engine = this._engines[engineName];
  2981.       if (engine && engine.alias == aAlias)
  2982.         return engine;
  2983.     }
  2984.     return null;
  2985.   },
  2986.  
  2987.   addEngineWithDetails: function SRCH_SVC_addEWD(aName, aIconURL, aAlias,
  2988.                                                  aDescription, aMethod,
  2989.                                                  aTemplate) {
  2990.     if (!aName)
  2991.       FAIL("Invalid name passed to addEngineWithDetails!");
  2992.     if (!aMethod)
  2993.       FAIL("Invalid method passed to addEngineWithDetails!");
  2994.     if (!aTemplate)
  2995.       FAIL("Invalid template passed to addEngineWithDetails!");
  2996.     if (this._engines[aName])
  2997.       FAIL("An engine with that name already exists!", Cr.NS_ERROR_FILE_ALREADY_EXISTS);
  2998.  
  2999.     var engine = new Engine(getSanitizedFile(aName), SEARCH_DATA_XML, false);
  3000.     engine._initFromMetadata(aName, aIconURL, aAlias, aDescription,
  3001.                              aMethod, aTemplate);
  3002.     this._addEngineToStore(engine);
  3003.     this._batchCacheInvalidation();
  3004.   },
  3005.  
  3006.   addEngine: function SRCH_SVC_addEngine(aEngineURL, aDataType, aIconURL,
  3007.                                          aConfirm) {
  3008.     LOG("addEngine: Adding \"" + aEngineURL + "\".");
  3009.     try {
  3010.       var uri = makeURI(aEngineURL);
  3011.       var engine = new Engine(uri, aDataType, false);
  3012.       engine._initFromURI();
  3013.     } catch (ex) {
  3014.       FAIL("addEngine: Error adding engine:\n" + ex, Cr.NS_ERROR_FAILURE);
  3015.     }
  3016.     engine._setIcon(aIconURL, false);
  3017.     engine._confirm = aConfirm;
  3018.   },
  3019.  
  3020.   removeEngine: function SRCH_SVC_removeEngine(aEngine) {
  3021.     if (!aEngine)
  3022.       FAIL("no engine passed to removeEngine!");
  3023.  
  3024.     var engineToRemove = null;
  3025.     for (var e in this._engines)
  3026.       if (aEngine.wrappedJSObject == this._engines[e])
  3027.         engineToRemove = this._engines[e];
  3028.  
  3029.     if (!engineToRemove)
  3030.       FAIL("removeEngine: Can't find engine to remove!", Cr.NS_ERROR_FILE_NOT_FOUND);
  3031.  
  3032.     if (engineToRemove == this.currentEngine)
  3033.       this._currentEngine = null;
  3034.  
  3035.     if (engineToRemove._readOnly) {
  3036.       // Just hide it (the "hidden" setter will notify) and remove its alias to
  3037.       // avoid future conflicts with other engines.
  3038.       engineToRemove.hidden = true;
  3039.       engineToRemove.alias = null;
  3040.     } else {
  3041.       // Cancel the lazy serialization timer if it's running
  3042.       if (engineToRemove._serializeTimer) {
  3043.         engineToRemove._serializeTimer.cancel();
  3044.         engineToRemove._serializeTimer = null;
  3045.       }
  3046.  
  3047.       // Remove the engine file from disk (this might throw)
  3048.       engineToRemove._remove();
  3049.       engineToRemove._file = null;
  3050.  
  3051.       // Remove the engine from _sortedEngines
  3052.       var index = this._sortedEngines.indexOf(engineToRemove);
  3053.       if (index == -1)
  3054.         FAIL("Can't find engine to remove in _sortedEngines!", Cr.NS_ERROR_FAILURE);
  3055.       this._sortedEngines.splice(index, 1);
  3056.  
  3057.       // Remove the engine from the internal store
  3058.       delete this._engines[engineToRemove.name];
  3059.  
  3060.       notifyAction(engineToRemove, SEARCH_ENGINE_REMOVED);
  3061.  
  3062.       // Since we removed an engine, we need to update the preferences.
  3063.       this._needToSetOrderPrefs = true;
  3064.     }
  3065.   },
  3066.  
  3067.   moveEngine: function SRCH_SVC_moveEngine(aEngine, aNewIndex) {
  3068.     if ((aNewIndex > this._sortedEngines.length) || (aNewIndex < 0))
  3069.       FAIL("SRCH_SVC_moveEngine: Index out of bounds!");
  3070.     if (!(aEngine instanceof Ci.nsISearchEngine))
  3071.       FAIL("SRCH_SVC_moveEngine: Invalid engine passed to moveEngine!");
  3072.     if (aEngine.hidden)
  3073.       FAIL("moveEngine: Can't move a hidden engine!", Cr.NS_ERROR_FAILURE);
  3074.  
  3075.     var engine = aEngine.wrappedJSObject;
  3076.  
  3077.     var currentIndex = this._sortedEngines.indexOf(engine);
  3078.     if (currentIndex == -1)
  3079.       FAIL("moveEngine: Can't find engine to move!", Cr.NS_ERROR_UNEXPECTED);
  3080.  
  3081.     // Our callers only take into account non-hidden engines when calculating
  3082.     // aNewIndex, but we need to move it in the array of all engines, so we
  3083.     // need to adjust aNewIndex accordingly. To do this, we count the number
  3084.     // of hidden engines in the list before the engine that we're taking the
  3085.     // place of. We do this by first finding newIndexEngine (the engine that
  3086.     // we were supposed to replace) and then iterating through the complete 
  3087.     // engine list until we reach it, increasing aNewIndex for each hidden
  3088.     // engine we find on our way there.
  3089.     //
  3090.     // This could be further simplified by having our caller pass in
  3091.     // newIndexEngine directly instead of aNewIndex.
  3092.     var newIndexEngine = this._getSortedEngines(false)[aNewIndex];
  3093.     if (!newIndexEngine)
  3094.       FAIL("moveEngine: Can't find engine to replace!", Cr.NS_ERROR_UNEXPECTED);
  3095.  
  3096.     for (var i = 0; i < this._sortedEngines.length; ++i) {
  3097.       if (newIndexEngine == this._sortedEngines[i])
  3098.         break;
  3099.       if (this._sortedEngines[i].hidden)
  3100.         aNewIndex++;
  3101.     }
  3102.  
  3103.     if (currentIndex == aNewIndex)
  3104.       return; // nothing to do!
  3105.  
  3106.     // Move the engine
  3107.     var movedEngine = this._sortedEngines.splice(currentIndex, 1)[0];
  3108.     this._sortedEngines.splice(aNewIndex, 0, movedEngine);
  3109.  
  3110.     notifyAction(engine, SEARCH_ENGINE_CHANGED);
  3111.  
  3112.     // Since we moved an engine, we need to update the preferences.
  3113.     this._needToSetOrderPrefs = true;
  3114.   },
  3115.  
  3116.   restoreDefaultEngines: function SRCH_SVC_resetDefaultEngines() {
  3117.     for each (var e in this._engines) {
  3118.       // Unhide all default engines
  3119.       if (e.hidden && e._isDefault)
  3120.         e.hidden = false;
  3121.     }
  3122.   },
  3123.  
  3124.   get defaultEngine() {
  3125.     const defPref = BROWSER_SEARCH_PREF + "defaultenginename";
  3126.     // Get the default engine - this pref should always exist, but the engine
  3127.     // might be hidden
  3128.     this._defaultEngine = this.getEngineByName(getLocalizedPref(defPref, ""));
  3129.     if (!this._defaultEngine || this._defaultEngine.hidden)
  3130.       this._defaultEngine = this._getSortedEngines(false)[0] || null;
  3131.     return this._defaultEngine;
  3132.   },
  3133.  
  3134.   get currentEngine() {
  3135.     if (!this._currentEngine || this._currentEngine.hidden)
  3136.       this._currentEngine = this.defaultEngine;
  3137.     return this._currentEngine;
  3138.   },
  3139.   set currentEngine(val) {
  3140.     if (!(val instanceof Ci.nsISearchEngine))
  3141.       FAIL("Invalid argument passed to currentEngine setter");
  3142.  
  3143.     var newCurrentEngine = this.getEngineByName(val.name);
  3144.     if (!newCurrentEngine)
  3145.       FAIL("Can't find engine in store!", Cr.NS_ERROR_UNEXPECTED);
  3146.  
  3147.     this._currentEngine = newCurrentEngine;
  3148.  
  3149.     var currentEnginePref = BROWSER_SEARCH_PREF + "selectedEngine";
  3150.  
  3151.     if (this._currentEngine == this.defaultEngine) {
  3152.       if (gPrefSvc.prefHasUserValue(currentEnginePref))
  3153.         gPrefSvc.clearUserPref(currentEnginePref);
  3154.     }
  3155.     else {
  3156.       setLocalizedPref(currentEnginePref, this._currentEngine.name);
  3157.     }
  3158.  
  3159.     notifyAction(this._currentEngine, SEARCH_ENGINE_CURRENT);
  3160.   },
  3161.  
  3162.   // nsIObserver
  3163.   observe: function SRCH_SVC_observe(aEngine, aTopic, aVerb) {
  3164.     switch (aTopic) {
  3165.       case SEARCH_ENGINE_TOPIC:
  3166.         switch (aVerb) {
  3167.           case SEARCH_ENGINE_LOADED:
  3168.             var engine = aEngine.QueryInterface(Ci.nsISearchEngine);
  3169.             LOG("nsSearchService::observe: Done installation of " + engine.name
  3170.                 + ".");
  3171.             this._addEngineToStore(engine.wrappedJSObject);
  3172.             if (engine.wrappedJSObject._useNow) {
  3173.               LOG("nsSearchService::observe: setting current");
  3174.               this.currentEngine = aEngine;
  3175.             }
  3176.             this._batchCacheInvalidation();
  3177.             break;
  3178.           case SEARCH_ENGINE_CHANGED:
  3179.           case SEARCH_ENGINE_REMOVED:
  3180.             this._batchCacheInvalidation();
  3181.             break;
  3182.         }
  3183.         break;
  3184.  
  3185.       case QUIT_APPLICATION_TOPIC:
  3186.         this._removeObservers();
  3187.         this._saveSortedEngineList();
  3188.         if (this._batchTimer) {
  3189.           // Flush to disk immediately
  3190.           this._batchTimer.cancel();
  3191.           this._buildCache();
  3192.         }
  3193.         break;
  3194.     }
  3195.   },
  3196.  
  3197.   _addObservers: function SRCH_SVC_addObservers() {
  3198.     gObsSvc.addObserver(this, SEARCH_ENGINE_TOPIC, false);
  3199.     gObsSvc.addObserver(this, QUIT_APPLICATION_TOPIC, false);
  3200.   },
  3201.  
  3202.   _removeObservers: function SRCH_SVC_removeObservers() {
  3203.     gObsSvc.removeObserver(this, SEARCH_ENGINE_TOPIC);
  3204.     gObsSvc.removeObserver(this, QUIT_APPLICATION_TOPIC);
  3205.   },
  3206.  
  3207.   QueryInterface: function SRCH_SVC_QI(aIID) {
  3208.     if (aIID.equals(Ci.nsIBrowserSearchService) ||
  3209.         aIID.equals(Ci.nsIObserver)             ||
  3210.         aIID.equals(Ci.nsISupports))
  3211.       return this;
  3212.     throw Cr.NS_ERROR_NO_INTERFACE;
  3213.   }
  3214. };
  3215.  
  3216. var engineMetadataService = {
  3217.   init: function epsInit() {
  3218.     var engineDataTable = "id INTEGER PRIMARY KEY, engineid STRING, name STRING, value STRING";
  3219.     var file = getDir(NS_APP_USER_PROFILE_50_DIR);
  3220.     file.append("search.sqlite");
  3221.     var dbService = Cc["@mozilla.org/storage/service;1"].
  3222.                     getService(Ci.mozIStorageService);
  3223.     try {
  3224.         this.mDB = dbService.openDatabase(file);
  3225.     } catch (ex) {
  3226.         if (ex.result == 0x8052000b) { /* NS_ERROR_FILE_CORRUPTED */
  3227.             // delete and try again
  3228.             file.remove(false);
  3229.             this.mDB = dbService.openDatabase(file);
  3230.         } else {
  3231.             throw ex;
  3232.         }
  3233.     }
  3234.  
  3235.     try {
  3236.       this.mDB.createTable("engine_data", engineDataTable);
  3237.     } catch (ex) {
  3238.       // Fails if the table already exists, which is fine
  3239.     }
  3240.  
  3241.     this.mGetData = createStatement (
  3242.       this.mDB,
  3243.       "SELECT value FROM engine_data WHERE engineid = :engineid AND name = :name");
  3244.     this.mDeleteData = createStatement (
  3245.       this.mDB,
  3246.       "DELETE FROM engine_data WHERE engineid = :engineid AND name = :name");
  3247.     this.mInsertData = createStatement (
  3248.       this.mDB,
  3249.       "INSERT INTO engine_data (engineid, name, value) " +
  3250.       "VALUES (:engineid, :name, :value)");
  3251.   },
  3252.   getAttr: function epsGetAttr(engine, name) {
  3253.      // attr names must be lower case
  3254.      name = name.toLowerCase();
  3255.  
  3256.     var stmt = this.mGetData;
  3257.     stmt.reset();
  3258.     var pp = stmt.params;
  3259.     pp.engineid = engine._id;
  3260.     pp.name = name;
  3261.  
  3262.     var value = null;
  3263.     if (stmt.step())
  3264.       value = stmt.row.value;
  3265.     stmt.reset();
  3266.     return value;
  3267.   },
  3268.  
  3269.   setAttr: function epsSetAttr(engine, name, value) {
  3270.     // attr names must be lower case
  3271.     name = name.toLowerCase();
  3272.  
  3273.     this.mDB.beginTransaction();
  3274.  
  3275.     var pp = this.mDeleteData.params;
  3276.     pp.engineid = engine._id;
  3277.     pp.name = name;
  3278.     this.mDeleteData.step();
  3279.     this.mDeleteData.reset();
  3280.  
  3281.     pp = this.mInsertData.params;
  3282.     pp.engineid = engine._id;
  3283.     pp.name = name;
  3284.     pp.value = value;
  3285.     this.mInsertData.step();
  3286.     this.mInsertData.reset();
  3287.  
  3288.     this.mDB.commitTransaction();
  3289.   },
  3290.  
  3291.   setAttrs: function epsSetAttrs(engines, names, values) {
  3292.     this.mDB.beginTransaction();
  3293.  
  3294.     for (var i = 0; i < engines.length; i++) {
  3295.       // attr names must be lower case
  3296.       var name = names[i].toLowerCase();
  3297.  
  3298.       var pp = this.mDeleteData.params;
  3299.       pp.engineid = engines[i]._id;
  3300.       pp.name = names[i];
  3301.       this.mDeleteData.step();
  3302.       this.mDeleteData.reset();
  3303.  
  3304.       pp = this.mInsertData.params;
  3305.       pp.engineid = engines[i]._id;
  3306.       pp.name = names[i];
  3307.       pp.value = values[i];
  3308.       this.mInsertData.step();
  3309.       this.mInsertData.reset();
  3310.     }
  3311.  
  3312.     this.mDB.commitTransaction();
  3313.   },
  3314.  
  3315.   deleteEngineData: function epsDelData(engine, name) {
  3316.     // attr names must be lower case
  3317.     name = name.toLowerCase();
  3318.  
  3319.     var pp = this.mDeleteData.params;
  3320.     pp.engineid = engine._id;
  3321.     pp.name = name;
  3322.     this.mDeleteData.step();
  3323.     this.mDeleteData.reset();
  3324.   }
  3325. }
  3326.  
  3327. const SEARCH_UPDATE_LOG_PREFIX = "*** Search update: ";
  3328.  
  3329. /**
  3330.  * Outputs aText to the JavaScript console as well as to stdout, if the search
  3331.  * logging pref (browser.search.update.log) is set to true.
  3332.  */
  3333. function ULOG(aText) {
  3334.   if (getBoolPref(BROWSER_SEARCH_PREF + "update.log", false)) {
  3335.     dump(SEARCH_UPDATE_LOG_PREFIX + aText + "\n");
  3336.     var consoleService = Cc["@mozilla.org/consoleservice;1"].
  3337.                          getService(Ci.nsIConsoleService);
  3338.     consoleService.logStringMessage(aText);
  3339.   }
  3340. }
  3341.  
  3342. var engineUpdateService = {
  3343.   init: function eus_init() {
  3344.     var tm = Cc["@mozilla.org/updates/timer-manager;1"].
  3345.              getService(Ci.nsIUpdateTimerManager);
  3346.     // figure out how often to check for any expired engines
  3347.     var interval = gPrefSvc.getIntPref(BROWSER_SEARCH_PREF + "updateinterval");
  3348.  
  3349.     // Interval is stored in hours
  3350.     var seconds = interval * 3600;
  3351.     tm.registerTimer("search-engine-update-timer", engineUpdateService,
  3352.                      seconds);
  3353.   },
  3354.  
  3355.   scheduleNextUpdate: function eus_scheduleNextUpdate(aEngine) {
  3356.     var interval = aEngine._updateInterval || SEARCH_DEFAULT_UPDATE_INTERVAL;
  3357.     var milliseconds = interval * 86400000; // |interval| is in days
  3358.     engineMetadataService.setAttr(aEngine, "updateexpir",
  3359.                                   Date.now() + milliseconds);
  3360.   },
  3361.  
  3362.   update: function eus_Update(aEngine) {
  3363.     let engine = aEngine.wrappedJSObject;
  3364.     ULOG("update called for " + aEngine._name);
  3365.     if (!getBoolPref(BROWSER_SEARCH_PREF + "update", true) || !engine._hasUpdates)
  3366.       return;
  3367.  
  3368.     // We use the cache to store updated app engines, so refuse to update if the
  3369.     // cache is disabled.
  3370.     if (engine._readOnly &&
  3371.         !getBoolPref(BROWSER_SEARCH_PREF + "cache.enabled", true))
  3372.       return;
  3373.  
  3374.     let testEngine = null;
  3375.     let updateURL = engine._getURLOfType(URLTYPE_OPENSEARCH);
  3376.     let updateURI = (updateURL && updateURL._hasRelation("self")) ? 
  3377.                      updateURL.getSubmission("", engine).uri :
  3378.                      makeURI(engine._updateURL);
  3379.     if (updateURI) {
  3380.       if (engine._isDefault && !updateURI.schemeIs("https")) {
  3381.         ULOG("Invalid scheme for default engine update");
  3382.         return;
  3383.       }
  3384.  
  3385.       let dataType = engineMetadataService.getAttr(engine, "updatedatatype");
  3386.       if (!dataType) {
  3387.         ULOG("No loadtype to update engine!");
  3388.         return;
  3389.       }
  3390.  
  3391.       ULOG("updating " + engine.name + " from " + updateURI.spec);
  3392.       testEngine = new Engine(updateURI, dataType, false);
  3393.       testEngine._engineToUpdate = engine;
  3394.       testEngine._initFromURI();
  3395.     } else
  3396.       ULOG("invalid updateURI");
  3397.  
  3398.     if (engine._iconUpdateURL) {
  3399.       // If we're updating the engine too, use the new engine object,
  3400.       // otherwise use the existing engine object.
  3401.       (testEngine || engine)._setIcon(engine._iconUpdateURL, true);
  3402.     }
  3403.   },
  3404.  
  3405.   notify: function eus_Notify(aTimer) {
  3406.     ULOG("notify called");
  3407.  
  3408.     if (!getBoolPref(BROWSER_SEARCH_PREF + "update", true))
  3409.       return;
  3410.  
  3411.     // Our timer has expired, but unfortunately, we can't get any data from it.
  3412.     // Therefore, we need to walk our engine-list, looking for expired engines
  3413.     var searchService = Cc["@mozilla.org/browser/search-service;1"].
  3414.                         getService(Ci.nsIBrowserSearchService);
  3415.     var currentTime = Date.now();
  3416.     ULOG("currentTime: " + currentTime);
  3417.     for each (engine in searchService.getEngines({})) {
  3418.       engine = engine.wrappedJSObject;
  3419.       if (!engine._hasUpdates)
  3420.         continue;
  3421.  
  3422.       ULOG("checking " + engine.name);
  3423.  
  3424.       var expirTime = engineMetadataService.getAttr(engine, "updateexpir");
  3425.       ULOG("expirTime: " + expirTime + "\nupdateURL: " + engine._updateURL +
  3426.            "\niconUpdateURL: " + engine._iconUpdateURL);
  3427.  
  3428.       var engineExpired = expirTime <= currentTime;
  3429.  
  3430.       if (!expirTime || !engineExpired) {
  3431.         ULOG("skipping engine");
  3432.         continue;
  3433.       }
  3434.  
  3435.       ULOG(engine.name + " has expired");
  3436.  
  3437.       this.update(engine);
  3438.  
  3439.       // Schedule the next update
  3440.       this.scheduleNextUpdate(engine);
  3441.  
  3442.     } // end engine iteration
  3443.   }
  3444. };
  3445.  
  3446. const kClassID    = Components.ID("{7319788a-fe93-4db3-9f39-818cf08f4256}");
  3447. const kClassName  = "Browser Search Service";
  3448. const kContractID = "@mozilla.org/browser/search-service;1";
  3449.  
  3450. // nsIFactory
  3451. const kFactory = {
  3452.   createInstance: function (outer, iid) {
  3453.     if (outer != null)
  3454.       throw Cr.NS_ERROR_NO_AGGREGATION;
  3455.     return (new SearchService()).QueryInterface(iid);
  3456.   }
  3457. };
  3458.  
  3459. // nsIModule
  3460. const gModule = {
  3461.   registerSelf: function (componentManager, fileSpec, location, type) {
  3462.     componentManager.QueryInterface(Ci.nsIComponentRegistrar);
  3463.     componentManager.registerFactoryLocation(kClassID,
  3464.                                              kClassName,
  3465.                                              kContractID,
  3466.                                              fileSpec, location, type);
  3467.   },
  3468.  
  3469.   unregisterSelf: function(componentManager, fileSpec, location) {
  3470.     componentManager.QueryInterface(Ci.nsIComponentRegistrar);
  3471.     componentManager.unregisterFactoryLocation(kClassID, fileSpec);
  3472.   },
  3473.  
  3474.   getClassObject: function (componentManager, cid, iid) {
  3475.     if (!cid.equals(kClassID))
  3476.       throw Cr.NS_ERROR_NO_INTERFACE;
  3477.     if (!iid.equals(Ci.nsIFactory))
  3478.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  3479.     return kFactory;
  3480.   },
  3481.  
  3482.   canUnload: function (componentManager) {
  3483.     return true;
  3484.   }
  3485. };
  3486.  
  3487. function NSGetModule(componentManager, fileSpec) {
  3488.   return gModule;
  3489. }
  3490.  
  3491. //@line 44 "/build/buildd/xulrunner-1.9.1-1.9.1.5+nobinonly/build-tree/mozilla/toolkit/content/debug.js"
  3492.  
  3493. var EXPORTED_SYMBOLS = ["NS_ASSERT"];
  3494.  
  3495. var gTraceOnAssert = true;
  3496.  
  3497. /**
  3498.  * This function provides a simple assertion function for JavaScript.
  3499.  * If the condition is true, this function will do nothing.  If the
  3500.  * condition is false, then the message will be printed to the console
  3501.  * and an alert will appear showing a stack trace, so that the (alpha
  3502.  * or nightly) user can file a bug containing it.  For future enhancements, 
  3503.  * see bugs 330077 and 330078.
  3504.  *
  3505.  * To suppress the dialogs, you can run with the environment variable
  3506.  * XUL_ASSERT_PROMPT set to 0 (if unset, this defaults to 1).
  3507.  *
  3508.  * @param condition represents the condition that we're asserting to be
  3509.  *                  true when we call this function--should be
  3510.  *                  something that can be evaluated as a boolean.
  3511.  * @param message   a string to be displayed upon failure of the assertion
  3512.  */
  3513.  
  3514. function NS_ASSERT(condition, message) {
  3515.   if (condition)
  3516.     return;
  3517.  
  3518.   var releaseBuild = true;
  3519.   var defB = Components.classes["@mozilla.org/preferences-service;1"]
  3520.                        .getService(Components.interfaces.nsIPrefService)
  3521.                        .getDefaultBranch(null);
  3522.   try {
  3523.     switch (defB.getCharPref("app.update.channel")) {
  3524.       case "nightly":
  3525.       case "beta":
  3526.       case "default":
  3527.         releaseBuild = false;
  3528.     }
  3529.   } catch(ex) {}
  3530.  
  3531.   var caller = arguments.callee.caller;
  3532.   var assertionText = "ASSERT: " + message + "\n";
  3533.  
  3534.   if (releaseBuild) {
  3535.     // Just report the error to the console
  3536.     Components.utils.reportError(assertionText);
  3537.     return;
  3538.   }
  3539.  
  3540.   // Otherwise, dump to stdout and launch an assertion failure dialog
  3541.   dump(assertionText);
  3542.  
  3543.   var stackText = "";
  3544.   if (gTraceOnAssert) {
  3545.     stackText = "Stack Trace: \n";
  3546.     var count = 0;
  3547.     while (caller) {
  3548.       stackText += count++ + ":" + caller.name + "(";
  3549.       for (var i = 0; i < caller.arguments.length; ++i) {
  3550.         var arg = caller.arguments[i];
  3551.         stackText += arg;
  3552.         if (i < caller.arguments.length - 1)
  3553.           stackText += ",";
  3554.       }
  3555.       stackText += ")\n";
  3556.       caller = caller.arguments.callee.caller;
  3557.     }
  3558.   }
  3559.  
  3560.   var environment = Components.classes["@mozilla.org/process/environment;1"].
  3561.                     getService(Components.interfaces.nsIEnvironment);
  3562.   if (environment.exists("XUL_ASSERT_PROMPT") &&
  3563.       !parseInt(environment.get("XUL_ASSERT_PROMPT")))
  3564.     return;
  3565.  
  3566.   var source = null;
  3567.   if (this.window)
  3568.     source = this.window;
  3569.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
  3570.            getService(Components.interfaces.nsIPromptService);
  3571.   ps.alert(source, "Assertion Failed", assertionText + stackText);
  3572. }
  3573.